diff --git a/Documentation/ABI/testing/debugfs-ub-hisi-ubus b/Documentation/ABI/testing/debugfs-ub-hisi-ubus new file mode 100644 index 0000000000000000000000000000000000000000..69ba558bfc025c226bbb0c72ce3be7158f4ddd20 --- /dev/null +++ b/Documentation/ABI/testing/debugfs-ub-hisi-ubus @@ -0,0 +1,37 @@ +What: /sys/kernel/debug/UB_BUS_CTL/eu_table +Date: Oct 2025 +Contact: Junlong Zheng +Description: Display the contents of the EID-UPI entry. + By default, the EID and UPI key-value pair for entry 0 is displayed. + By writing an entry index to the properties file, you can retrieve + the content of the corresponding entry. + + Example:: + + Display the content of entry5: + # echo 5 > /sys/kernel/debug/UB_BUS_CTL/eu_table + # cat /sys/kernel/debug/UB_BUS_CTL/eu_table + +What: /sys/kernel/debug/UB_BUS_CTL/hi_msgq-/reg_info +Date: Oct 2025 +Contact: Junlong Zheng +Description: Display the register information for the specified queue of the designated + UB Bus controller. + +What: /sys/kernel/debug/UB_BUS_CTL/hi_msgq-/q_entry_info +Date: Oct 2025 +Contact: Junlong Zheng +Description: Display the SQE and CQE contents of the specified MSQG for the designated + UB Bus controller. By default, the content of SQ entry 0 is displayed. + By writing the queue type and entry index to the properties file, you can + retrieve the content of the corresponding entry. + + Example:: + + Output the content of SQ entry3: + # echo 0 3 > /sys/kernel/debug/UB_BUS_CTL/hi_msgq-/q_entry_info + # cat /sys/kernel/debug/UB_BUS_CTL/hi_msgq-/q_entry_info + + Output the content of CQ entry5: + # echo 2 5 > /sys/kernel/debug/UB_BUS_CTL/hi_msgq-/q_entry_info + # cat /sys/kernel/debug/UB_BUS_CTL/hi_msgq-/q_entry_info \ No newline at end of file diff --git a/Documentation/ABI/testing/sysfs-bus-ub b/Documentation/ABI/testing/sysfs-bus-ub new file mode 100644 index 0000000000000000000000000000000000000000..f7b3193958b7735718176f372b424fda29102326 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-ub @@ -0,0 +1,427 @@ +What: /sys/bus/ub/cluster +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Indicates the current system operating mode: + 1 for cluster mode, 0 for Standalone mode. + +What: /sys/bus/ub/instance +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the list of bus instances created in the current system. + By default, it starts from the first one. Writing numbers into + the file can change the starting position of the output bus + instance. + +What: /sys/bus/ub/drivers/.../bind +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Writing an entity number to this file will cause the driver + to attempt to bind to the entity. This is useful for + overriding default bindings. The entity number is + the same as found in /sys/bus/ub/devices/. + For example:: + + # echo 00002 > /sys/bus/ub/drivers/sample/bind + +What: /sys/bus/ub/drivers/.../unbind +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Writing an entity number to this file will cause the + driver to attempt to unbind from the entity. This may be + useful when overriding default bindings. The entity + number is the same as found in /sys/bus/ub/devices/. + For example:: + + # echo 00002 > /sys/bus/ub/drivers/sample/unbind + +What: /sys/bus/ub/drivers/.../new_id +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Writing a device ID to this file will attempt to + dynamically add a new device ID to a UB device driver. + This may allow the driver to support more hardware than + was included in the driver's static device ID support + table at compile time. The format for the device ID is: + VVVV DDDD MVVV MMMM CCCC MMMM PPPP. That is Vendor ID, + Device ID, Module Vendor ID, Module ID, Class, Class Mask + and Private Driver Data. The Vendor ID and Device ID fields + are required, the rest are optional. Upon successfully + adding an ID, the driver will probe for the device and + attempt to bind to it. + For example:: + + # echo cc08 a001 > /sys/bus/ub/drivers/sample/new_id + +What: /sys/bus/ub/drivers/.../remove_id +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Writing a device ID to this file will remove an ID + that was dynamically added via the new_id sysfs entry. + The format for the device ID is: + VVVV DDDD MVVV MMMM CCCC MMMM. That is Vendor ID, Device + ID, Module Vendor ID, Module ID, Class and Class Mask. + The Vendor ID and Device ID fields are required, the rest + are optional. After successfully removing an ID, + the driver will no longer support the device. + This is useful to ensure auto probing won't + match the driver to the device. + For example:: + + # echo cc08 a001 > /sys/bus/ub/drivers/sample/remove_id + +What: /sys/bus/ub/devices/.../class_code +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the class code type of the entity. + +What: /sys/bus/ub/devices/.../config +Date: Oct 2025 +Contact: Junlong Zheng +Description: + A channel is provided for user-mode programs to access the + entity configuration space. User programs can open the file + using the open system call and then perform read/write + operations on the configuration space using the pread/pwrite + system calls. + For details, please refer to the implementation of ubutils + . + +What: /sys/bus/ub/devices/.../device +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the Device ID of the entity. + +What: /sys/bus/ub/devices/.../device_reset +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Writing 1 to this file can trigger a device-level reset. All + entities below it will be reset. + Supported only by Entity0. + +What: /sys/bus/ub/devices/.../direct_link +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the link connection relationships and the peer + information of the ports connected to this entity. + +What: /sys/bus/ub/devices/.../driver_override +Date: Oct 2025 +Contact: Junlong Zheng +Description: + This file allows the driver for a device to be specified which + will override standard static and dynamic ID matching. When + specified, only a driver with a name matching the value written + to driver_override will have an opportunity to bind to the + device. The override is specified by writing a string to the + driver_override file (echo sample > driver_override) and + may be cleared with an empty string (echo > driver_override). + This returns the device to standard matching rules binding. + Writing to driver_override does not automatically unbind the + device from its current driver or make any attempt to + automatically load the specified driver. If no driver with a + matching name is currently loaded in the kernel, the device + will not bind to any driver. This also allows devices to + opt-out of driver binding using a driver_override name such as + "none". Only a single driver may be specified in the override, + there is no support for parsing delimiters. + +What: /sys/bus/ub/devices/.../eid +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the entity's EID. + +What: /sys/bus/ub/devices/.../entity_idx +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the index of the entity, numbered starting from 0. + +What: /sys/bus/ub/devices/.../guid +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the GUID of the entity. + +What: /sys/bus/ub/devices/.../instance +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the EID of the bus instance bound to the entity. + +What: /sys/bus/ub/devices/.../kref +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the current reference count of the entity. + +What: /sys/bus/ub/devices/.../match_driver +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Indicates whether the current entity is ready for driver + matching. Some entities require additional initialization work, + so this entry is provided to control the entity separately. + In this case, it is necessary to ensure a certain timing + sequence; For example, the driver should be loaded only after + this status of the entity is set to 1 to ensure that the driver + probe is correctly initiated. + +What: /sys/bus/ub/devices/.../numa +Date: Oct 2025 +Contact: Junlong Zheng +Description: + This file contains the NUMA node to which the UB Entity is + attached, or -1 if the node is unknown. The initial value + comes from UBRT table, defined in the UB firmware specification. + +What: /sys/bus/ub/devices/.../primary_cna +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the primary compact network address of the entity. + +What: /sys/bus/ub/devices/.../primary_entity +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the entity number of the entity0 to which this entity + belongs. + +What: /sys/bus/ub/devices/.../mue_list +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display a list of all MUEs under this entity, excluding itself. + Only Entity0 has this attribute file. + +What: /sys/bus/ub/devices/.../reset +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Writing 1 to this file can trigger an entity-level reset, only + reset this entity, it will not affect other entities. + +What: /sys/bus/ub/devices/.../resource +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Information about the resource space of the entity is displayed, + with a total of 3 entries, each consisting of the following + three components: start_address, end_address, flags. + If all values are 0, it indicates that the resource space is + not supported. + +What: /sys/bus/ub/devices/.../resource +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Provide attribute files to the user-mode driver. Through the + open and mmap system calls, the resource space of an entity can + be mapped into the process space for direct access, thereby + improving the efficiency of cross-mode resource space access. + The memory attribute mapped by this interface is the device + attribute. + +What: /sys/bus/ub/devices/.../resource_wc +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Provide attribute files to the user-mode driver. Through the + open and mmap system calls, the resource space of an entity can + be mapped into the process space for direct access, thereby + improving the efficiency of cross-mode resource space access. + The memory attribute mapped by this interface is the + write-combine attribute. + +What: /sys/bus/ub/devices/.../sw_cap +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display whether forwarding capability is supported. + Only UBC supports it. + +What: /sys/bus/ub/devices/.../tid +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the Token ID of the entity. The entity uses this + Token ID to access system memory. + +What: /sys/bus/ub/devices/.../type +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the type of the entity. The type is a subdomain segment + of GUID. + +What: /sys/bus/ub/devices/.../ubc +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the entity number of the UB controller associated with + the entity. + +What: /sys/bus/ub/devices/.../ub_numues +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the number of UEs that have been enabled for this + entity. Writing a value to the file enables the UEs. The written + value must be within the range of ub_totalues. Writing 0 + disables all UEs. + Only MUE supports this file. + +What: /sys/bus/ub/devices/.../ub_total_entities +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the number of all entities supported by this entity. + Only Entity0 supports this file. + +What: /sys/bus/ub/devices/.../ub_totalues +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the number of UEs owned by this entity. + Only MUE supports this file. + +What: /sys/bus/ub/devices/.../ue_list +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display a list of all UEs under this entity, excluding itself. + Only MUE has this attribute file. + +What: /sys/bus/ub/devices/.../upi +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display UPI of the entity. + +What: /sys/bus/ub/devices/.../vendor +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display Vendor ID of the entity. + +What: /sys/bus/ub/devices/.../msi_irqs/ +Date: Oct 2025 +Contact: Junlong Zheng +Description: + The /sys/bus/ub/devices/.../msi_irqs/ directory contains a + variable set of files, with each file being named after a + corresponding msi irq vector allocated to that entity. + +What: /sys/bus/ub/devices/.../msi_irqs/ +Date: Oct 2025 +Contact: Junlong Zheng +Description: + This attribute indicates the mode that the irq vector named by + the file is in (msi vs. msix) + +What: /sys/bus/ub/devices/.../port/asy_link_width +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Indicates whether the port supports asymmetric link width. + Supported only on physical port. + + +What: /sys/bus/ub/devices/.../port/boundary +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Indicates whether the port is a boundary port. + +What: /sys/bus/ub/devices/.../port/cna +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display the CNA of this port. + +What: /sys/bus/ub/devices/.../port/glb_qdlws +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Write 1 to enable global dynamic lane adjustment, + write 0 to disable this function. + Supported only on physical port. + +What: /sys/bus/ub/devices/.../port/linkup +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Display whether the port has established a connection. + +What: /sys/bus/ub/devices/.../port/neighbor +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Displays the entity number of the peer entity at the port. + If no link is established, it displays "No Neighbor". + +What: /sys/bus/ub/devices/.../port/neighbor_guid +Date: Oct 2025 +Contact: Junlong Zheng +Description: + If there is a peer entity, display the GUID of the peer entity. + Otherwise, display "No Neighbor". + +What: /sys/bus/ub/devices/.../port/neighbor_port_idx +Date: Oct 2025 +Contact: Junlong Zheng +Description: + If there is a peer entity, the port index of the peer entity is + displayed. Otherwise, display "No Neighbor". + +What: /sys/bus/ub/devices/.../port/port_reset +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Supports individual port reset, triggered by writing 1. + Supported only on physical port. + +What: /sys/bus/ub/devices/.../port/qdlws_exec_state +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Obtain the hardware execution status of the current dynamically + adjustable lane. + Supported only on physical port. + +What: /sys/bus/ub/devices/.../port/rx_qdlws +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Enable/Disable dynamic lane adjustment in the RX direction. + Supported only on physical port. + +What: /sys/bus/ub/devices/.../port/tx_qdlws +Date: Oct 2025 +Contact: Junlong Zheng +Description: + Enable/Disable dynamic lane adjustment in the TX direction. + Supported only on physical port. + +What: /sys/bus/ub/devices/.../slot/power +Date: Oct 2025 +Contact: Junlong Zheng +Description: + This feature supports hot-plug notification. + Display the current slot status, the value can be "on", + "poweron", "poweroff", "off" or "unknown state". And can + write 1 to enable power on the slot, and write 0 to + power it off. + This file is supported only by entities that support + hot-plug features. diff --git a/Documentation/devicetree/bindings/ub/hisi,ubc.yaml b/Documentation/devicetree/bindings/ub/hisi,ubc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2219dd7902c1b075b2ac994ba6418f4c02ebbe90 --- /dev/null +++ b/Documentation/devicetree/bindings/ub/hisi,ubc.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ub/hisi,ubc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon UBC (Unified Bus Controller) platform device + +maintainers: + - Yuhao Xiang + +description: |+ + This platform device was added to enable the automatic loading of the + hisi_ubus driver. If this feature is not needed, you can omit adding this + device and manually load the driver instead. + +properties: + $nodename: + pattern: "^hisi-ubc$" + description: | + The node name should be "hisi-ubc". + + compatible: + const: "hisi,ubc" + description: | + The compatible property should be "hisi,ubc" to identify the device as a + HiSilicon UBC platform device. + +unevaluatedProperties: false + +examples: + - |+ + hisi-ubc { + compatible = "hisi,ubc"; + }; diff --git a/Documentation/devicetree/bindings/ub/ub,ubc.yaml b/Documentation/devicetree/bindings/ub/ub,ubc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..012a293cf9e2532f13f5926792e7fab820e6fe3b --- /dev/null +++ b/Documentation/devicetree/bindings/ub/ub,ubc.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ub/ub,ubc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: UBC (Unified Bus Controller) platform device + +maintainers: + - Yuhao Xiang + +description: | + The UBC platform device reported UBC interrupt number and the association + between the ubc and the interrupt controller. + +properties: + $nodename: + pattern: "^ubc@[0-9a-f]*" + + compatible: + const: "ub,ubc" + + interrupts: + maxItems: 1 + description: | + The interrupt specifier for the UBC. Used by the msgq of the ub controller. + + index: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + The index of the UBC. This is used to identify the specific UBC + in a system with multiple UBC devices. Starts from 0. + + msi-parent: + description: The msi interrupt for the UBC. Used by the ub entity connected + to UBC. + +required: + - compatible + - interrupts + - index + - msi-parent + +unevaluatedProperties: true + +examples: + - |+ + ubc@0 { + compatible = "ub,ubc"; + #interrupt-cells = <0x3>; + interrupt-parent = <0x01>; + interrupts = <0x00 0xcb 0x4>; + index = <0x00>; + msi-parent = <&its 0x54c0>; + }; diff --git a/Documentation/driver-api/ub/index.rst b/Documentation/driver-api/ub/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..d3a5969e6e947f98cfeaaba4683cc09f5f2372e4 --- /dev/null +++ b/Documentation/driver-api/ub/index.rst @@ -0,0 +1,18 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +:Copyright: |copy| 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + +============================================= +The Linux UnifiedBus implementer's API guide +============================================= + +.. class:: toc-title + + Table of contents + +.. toctree:: + :maxdepth: 2 + + ubfi + ubus + ubase \ No newline at end of file diff --git a/Documentation/driver-api/ub/ubfi.rst b/Documentation/driver-api/ub/ubfi.rst new file mode 100644 index 0000000000000000000000000000000000000000..a7b0466bdca487b142b4639d29a1d27f81d054f1 --- /dev/null +++ b/Documentation/driver-api/ub/ubfi.rst @@ -0,0 +1,7 @@ +.. SPDX-License-Identifier: GPL-2.0 + +UBFI Driver Support Library +--------------------------- + +.. kernel-doc:: include/ub/ubfi/ubfi.h + :functions: diff --git a/Documentation/driver-api/ub/ubus.rst b/Documentation/driver-api/ub/ubus.rst new file mode 100644 index 0000000000000000000000000000000000000000..4e39b79e9e5a301689486f07019375f51f924786 --- /dev/null +++ b/Documentation/driver-api/ub/ubus.rst @@ -0,0 +1,7 @@ +.. SPDX-License-Identifier: GPL-2.0 + +UBUS Driver Support Library +----------------------------- + +.. kernel-doc:: include/ub/ubus/ubus.h + :functions: diff --git a/Documentation/ub/index.rst b/Documentation/ub/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..8e939a2ba8fec9c02d629b3db74271c9712a3d15 --- /dev/null +++ b/Documentation/ub/index.rst @@ -0,0 +1,15 @@ +.. SPDX-License-Identifier: GPL-2.0+ +.. include:: + +:Copyright: |copy| 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + +===================== +UnifiedBus Subsystem +===================== + +.. toctree:: + :maxdepth: 2 + + ubase/index + ubfi/index + ubus/index diff --git a/Documentation/ub/ubfi/index.rst b/Documentation/ub/ubfi/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..2dd11600f4f747abea30417efd1dea828a096f17 --- /dev/null +++ b/Documentation/ub/ubfi/index.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================= +UB Firmware Spec Driver +======================= + +.. toctree:: + :maxdepth: 2 + :numbered: + + ubfi diff --git a/Documentation/ub/ubfi/ubfi.rst b/Documentation/ub/ubfi/ubfi.rst new file mode 100644 index 0000000000000000000000000000000000000000..efea335726b81fbeb1b1ba5d7b119545b81c0ca1 --- /dev/null +++ b/Documentation/ub/ubfi/ubfi.rst @@ -0,0 +1,178 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=========== +UBFI Driver +=========== + +What is UBFI +============ + +When BIOS boots the OS with UB firmware, it should report the UB-related +information in the system so that the OS can obtain the UB-related information, +including UBC, UMMU, and all other information required for UB enabling. + +Startup information is related to chip specifications and is static information +that can be reported through a static information table. There are three +reporting methods: UBIOS, ACPI, and Device Tree. The only difference among these +three methods lies in the entry points for obtaining the UB-related information +tables. The contents of each information table remain consistent. + +UnifiedBus Firmware Interface (UBFI) driver supports obtaining UB-related +information from the BIOS via the ACPI table or device tree. And create software +instances of UBCs and UMMUs in the OS. + +UBFI driver is one of the fundamental drivers of UB. It has achieved the +aforementioned functions. + +.. code-block:: none + + +--------------------------------------------------------------+ + | BIOS | + +--------------------------------------------------------------+ + ^ ^ + |acpi of| + v v + +--------------------------------------------------------------+ + | kernel | + +--------------------------------------------------------------+ + ^ + | + v + +--------------------------------------------------------------+ + | ubfi | + +--------------------------------------------------------------+ + ^ ^ + | | + v v + +-----------------+ +-----------------+ + | ubus | | ummu | + +-----------------+ +-----------------+ + +What does UBFI do +================= + +When loading the ubfi driver, it detects the current OS boot mode and retrieves +the UBRT (UB root table) physical address from the BIOS. + + - ACPI (UBRT table) + - device tree (node: chosen: ubios-information-table) + +For the structure of UBRT, please refer to https://www.unifiedbus.com/ + +Create UBC +---------- + +BIOS may report information about multiple UBCs, some of which is shared among +multiple UBCs and is reported in ``struct ubrt_ubc_table`` + +.. kernel-doc:: drivers/ub/ubfi/ubc.h + :functions: ubrt_ubc_table + +As ``ubc_cna_start``, ``ubc_cna_end``, ``ubc_eid_start``, ``ubc_eid_end``, +``ubc_feature``, ``cluster_mode``, these attributes belong to the entire UBPU +node and are shared by all UBCs. + +For a single UBC, its information is reported in the ``struct ubc_node`` + +.. kernel-doc:: drivers/ub/ubfi/ubc.h + :functions: ubc_node + +We have performed the following work on a single UBC. + + - Create the UBC structure and record the UBC information + - Register the UBC irq with the kernel + - Initialize UBC and register the UBC device with the kernel + - Register the MMIO address space of UBC with the kernel + - Set the MSI domain for all UBCs + +After completing these steps, ``struct list_head ubc_list`` will be provided +externally, which records all UBCs within the node for subsequent +interconnection and communication purposes. + +Set MSI domain for UBC +~~~~~~~~~~~~~~~~~~~~~~ + +UBFI driver requests interrupts from the interrupt management subsystem on +behalf of the entity and delivers the interrupt configuration to the entity. +When reporting an interrupt, the entity writes the interrupt information into +the interrupt controller, which then calls back the interrupt management +subsystem. The interrupt management subsystem subsequently invokes the UB driver +to handle the corresponding interrupt. + +UB created a new Message Signaled Interrupt domain called USI (UB Signaled +Interrupt). + +UB will add a platform device in the DSDT and IORT tables to associate UBC +with the USI domain. If booting with device tree, we will add a new UBC node in +DTS for binding the USI domain. For each UBC, a corresponding number of platform +devices should be created. We will set the USI domain of these platform devices +to the USI domain of each UBC. + +Example in DTS for UBC:: + + ubc@N { + compatible = "ub,ubc"; + #interrupt-cells = <0x3>; + interrupt-parent = <0x01>; + interrupts = <0x0 0xa 0x4>; + index = <0x00>; + msi-parent = <0x1 0xabcd>; + }; + +Parse UMMU and PMU +------------------ + +Both UMMU and UMMU-PMU devices are platform devices and support creation via +ACPI and DTS. + +ACPI method: + - The device information for UMMU and UMMU-PMU has been added to DSDT and + IORT tables. + - When the OS enables ACPI functionality, the ACPI system will recognize + the device information in the DSDT and IORT tables and automatically + create platform devices for UMMU and UMMU-PMU. + - The number of platform devices for UMMU and UMMU-PMU depends on the + number of device information nodes described in the DSDT and IORT tables. + +DTS method: + - The DTB file has added device tree nodes for UMMU and UMMU-PMU. + - When the OS enables the device tree functionality, the DTS system will + recognize the device tree nodes for UMMU and UMMU-PMU, and then + automatically create platform devices for them. + - The number of platform devices for UMMU and UMMU-PMU depends on the + number of corresponding device tree nodes described in the device tree. + + Example in DTS for UMMU and UMMU-PMU:: + + ummu@N { + compatible = "ub,ummu"; + index = <0x0>; + msi-parent = <&its>; + }; + + ummu-pmu@N { + compatible = "ub,ummu_pmu"; + index = <0x0>; + msi-parent = <&its>; + }; + +Obtain UMMU nodes from the UBRT table: + - The UBRT table can be parsed to extract the UMMU sub-table, which contains + several UMMU nodes. Each UMMU node describes the hardware information of an + UMMU device and its corresponding UMMU-PMU device. The specific content of + UMMU nodes can be found in ``struct ummu_node``. + + - The number of UMMU platform devices created via ACPI or DTS should match the + number of UMMU nodes in the UBRT table, as they have a one-to-one + correspondence. The same one-to-one correspondence applies to UMMU-PMU + devices and UMMU nodes. + +Configure UMMU and PMU devices: + - For each UMMU node parsed from the UBRT table, the register information and + NUMA affinity described in the UMMU node can be configured for the + corresponding UMMU and UMMU-PMU devices. + - Each UMMU node's content is stored in the ``ubrt_fwnode_list`` linked list. + Subsequently, the corresponding UMMU node can be found by using the fwnode + property of the UMMU and UMMU-PMU devices, making it convenient to obtain the + hardware information during the initialization of the UMMU and UMMU-PMU + drivers. \ No newline at end of file diff --git a/Documentation/ub/ubus/hisi_ubus.rst b/Documentation/ub/ubus/hisi_ubus.rst new file mode 100644 index 0000000000000000000000000000000000000000..90e76d2e587f3abce0b68a24515895d77c9b82a7 --- /dev/null +++ b/Documentation/ub/ubus/hisi_ubus.rst @@ -0,0 +1,93 @@ +.. SPDX-License-Identifier: GPL-2.0 + +===================== +Hisilicon UBUS Driver +===================== + +Hisilicon UBUS Driver (abbreviated as Hisi UBUS) is a UnifiedBus (UB) +specification management subsystem specifically implemented for Hisi chips. It +provides a subsystem operation interfaces implementation:: + + static const struct ub_manage_subsystem_ops hisi_ub_manage_subsystem_ops = { + .vendor = HISI_VENDOR_ID, + .controller_probe = ub_bus_controller_probe, + .controller_remove = ub_bus_controller_remove, + .ras_handler_probe = ub_ras_handler_probe, + .ras_handler_remove = ub_ras_handler_remove + }; + +including probe/remove methods for the UB bus controller and ub ras handler. +Each specification management subsystem has a unique vendor id to identify the +provider. This vendor id is set to the vendor field of +``ub_manage_subsystem_ops`` implementation. During UB bus controller probe, a +ub_bus_controller_ops will be set to the UB bus controller, message device and +debug file system will be initialized. During UB bus controller remove, ops +will be unset, message device will be removed and debug file system will be +uninitialized. + +During module init, hisi_ub_manage_subsystem_ops is registered to Ubus driver +via the ``register_ub_manage_subsystem_ops()`` method provided by Ubus driver:: + + int register_ub_manage_subsystem_ops(const struct ub_manage_subsystem_ops *ops) + +When module is being unloaded, Ubus driver's +``unregister_ub_manage_subsystem_ops()`` is called to unregister the subsystem +operation interfaces:: + + void unregister_ub_manage_subsystem_ops(const struct ub_manage_subsystem_ops *ops) + +Hisi UBUS Controller Driver +=========================== +Hisi UBUS provides a ub bus controller operation interfaces implementation:: + + static struct ub_bus_controller_ops hi_ubc_ops = { + .eu_table_init = hi_eu_table_init, + .eu_table_uninit = hi_eu_table_uninit, + .eu_cfg = hi_eu_cfg, + .mem_decoder_create = hi_mem_decoder_create, + .mem_decoder_remove = hi_mem_decoder_remove, + .register_ubmem_irq = hi_register_ubmem_irq, + .unregister_ubmem_irq = hi_unregister_ubmem_irq, + .register_decoder_base_addr = hi_register_decoder_base_addr, + .entity_enable = hi_send_entity_enable_msg, + }; + +including init/uninit method for EID/UPI table, create/remove method for UB +memory decoder, register/unregister method for UB memory decoder interrupts +and so on. + +UB Message Core Driver +====================== +Hisi UBUS implements a message device that provides a set of operations:: + + static struct message_ops hi_message_ops = { + .sync_request = hi_message_sync_request, + .response = hi_message_response, + .sync_enum = hi_message_sync_enum, + .vdm_rx_handler = hi_vdm_rx_msg_handler, + .send = hi_message_send, + }; + +including synchronous message sending, synchronous enumeration message +sending, response message sending, vendor-defined message reception handling +and so on. After device creation, ``message_device_register()`` method of Ubus +driver is called to register the device to the Ubus driver message framework:: + + int message_device_register(struct message_device *mdev) + +This framework provides a unified interface for message transmission and +reception externally. + +Hisi UBUS Local Ras Error Handler +================================= +Hisi UBUS provides a local RAS handling module to detect and process errors +reported on the UB bus. It offers error printing and registry dump, determines +whether recovery is needed based on error type and severity, and can reset +ports for port issues in cluster environment. + +UB Vendor-Defined Messages Manager +================================== +Hisi UBUS defines several vendor-defined messages, implements messages' +transmission and processing. These private messages are mainly used for +managing the registration, release, and state control of physical and +virtual devices. \ No newline at end of file diff --git a/Documentation/ub/ubus/index.rst b/Documentation/ub/ubus/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..a4c2a58324cffef337bb2a98579ff818b3856d2e --- /dev/null +++ b/Documentation/ub/ubus/index.rst @@ -0,0 +1,13 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============= +UB BUS Driver +============= + +.. toctree:: + :maxdepth: 2 + :numbered: + + ubus + ubus-service + hisi_ubus \ No newline at end of file diff --git a/Documentation/ub/ubus/ubus-service.rst b/Documentation/ub/ubus/ubus-service.rst new file mode 100644 index 0000000000000000000000000000000000000000..fd347fff959a369c22aeab3bbd018fb2ad895f79 --- /dev/null +++ b/Documentation/ub/ubus/ubus-service.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=========================================== +UnifiedBus Bus Driver (UBUS Driver) Service +=========================================== + +The UnifiedBus (UB) specification describes RAS-related error handling and +notification-based hot-plug functionalities. The UBUS driver implements these +two types of functionalities as two independent services in software. This +article will separately introduce these two services. + +UB Device Driver Error Service +============================== +The UB specification defines three categories of protocol errors: A, B, and C. +Among these, A and B category protocol errors are directly handled by the +UB device driver, and thus will not be further discussed in this document. +C category protocol errors are reported to the UBUS Driver via the APEI +mechanism. The UBUS Driver provides a set of mechanisms for handling C category +protocol errors, which work in conjunction with the UB device driver to +complete the error handling process. + +The UBUS driver provides the ``struct ub_error_handlers`` structure, which +includes multiple callback functions related to error handling. The UB device +driver needs to implement these callback functions:: + + struct ub_error_handlers { + void (*ub_reset_prepare)(struct ub_entity *uent); + void (*ub_reset_done)(struct ub_entity *uent); + ub_ers_result_t (*ub_error_detected)(struct ub_entity *uent, ub_channel_state_t state); + ub_ers_result_t (*ub_resource_enabled)(struct ub_entity *uent); + }; + +For UB device driver: + + - ub_reset_prepare is called before ELR, serving to notify the device driver to + prepare for the work before ELR + - ub_reset_done is called after ELR, serving to notify the device driver that + ELR has completed and services can be resumed + - ub_error_detected is called when the UB bus driver detects an error, serving + to notify the UB device driver of the occurrence of an error + - ub_resource_enabled is called after the UB bus driver has completed error + handling, serving to notify the UB device driver that error handling has + completed + +Hot-Plug Service +================ +The UB specification defines the hot-plug functionality for devices, which +requires coordination between software and hardware. The UBUS driver implements +the hot removal and hot insertion of external devices on a per-slot basis. +For detailed procedures, please refer to the UB specification document. The main +functional points implemented by the UBUS driver include: + + - Button event handling, completing the processing of hot-plug and + hot-unplug button messages + - Indicator control, switching different knowledge points based on the + device status + - Power control, performing power on/off operations for slots based on the + device status + - Providing a user-space sysfs interface to simulate button effects + according to user commands diff --git a/Documentation/ub/ubus/ubus.rst b/Documentation/ub/ubus/ubus.rst new file mode 100644 index 0000000000000000000000000000000000000000..e7176e98732e626cdbf43fdee5e0ca17e0c66853 --- /dev/null +++ b/Documentation/ub/ubus/ubus.rst @@ -0,0 +1,312 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +How To Write Linux UB Device Drivers +====================================== + +UnifiedBus (abbreviated as UB) is an interconnection technology and +architecture designed for computing systems. It unifies the communication +between IO, memory access, and various processing units within the same +interconnection technology framework, enabling high-performance data transfer, +unified resource management, efficient collaboration, and effective programming +in computing systems. Resource management is one of its key features, +implemented through a combination of software and hardware. The UB Bus Driver +(referred to as the UBUS Driver) implements the software portion of this +feature. This document provides a brief overview of the components within the +UBUS Driver framework and how to develop UB device drivers within this driver +framework. See more on the UB spec . + +Composition of the UBUS Driver +============================== +The UBUS Driver consists of two parts. The first part is the common +implementation section, which will be developed according to the UB +specification requirements. The second part is the proprietary implementation by +each manufacturer, which is based on the specific circuit designs of each host +manufacturer. Each host manufacturer can provide differentiated functionalities +in this part of the code. + +If the UBUS subsystem is not configured (CONFIG_UB_UBUS is not set), most of +the UBUS functions described below are defined as inline functions either +completely empty or just returning an appropriate error codes to avoid +lots of ifdefs in the drivers. + +The figure below illustrates the internal composition and system boundaries of +the UBUS Driver. + +.. code-block:: none + + +----------------------------------------------------------+ + | ub device driver | + +----------------------------------------------------------+ + ^ + | + v + +----------------------------------------------------------+ + | ubus driver | + | | + | +--------------------------------------------------+ | + | | ubus driver vendor-specific | | + | +--------------------------------------------------+ | + | | + | +--------------------------------------------------+ | + | | ubus driver common | | + | | | | + | | +------+ +--------+ +------+ +-------+ +-----+ | | +---------+ + | | | enum | | config | | port | | route | | msg | | | <-> | GIC/ITS | + | | +------+ +--------+ +------+ +-------+ +-----+ | | +---------+ + | | +------------+ +--------+ +-----------+ | | + | | | controller | | entity | | interrupt | | | +------------+ + | | +------------+ +--------+ +-----------+ | | <-> | IOMMU/UMMU | + | | +---------+ +----------+ +------+ +----------+ | | +------------+ + | | | decoder | | resource | | pool | | instance | | | + | | +---------+ +----------+ +------+ +----------+ | | + | | +-----+ +------+ +-------+ +---------+ +-------+ | | + | | | ras | | link | | reset | | hotplug | | sysfs | | | + | | +-----+ +------+ +-------+ +---------+ +-------+ | | + | | +------+ +---------------+ | | + | | | ubfi | | bus framework | | | + | | +------+ +---------------+ | | + | +--------------------------------------------------+ | + +----------------------------------------------------------+ + ^ + | + v + +----------------------------------------------------------+ + | hardware/firmware | + +----------------------------------------------------------+ + +The following briefly describes the functions of each submodule within the +UBUS driver: + + - enum: implement network topology scanning and device enumeration + functionality + - config: enable access to the device configuration space + - port: manage device ports + - route: implement the configuration of the routing table + - msg: implement message assembly and transmission/reception processing + for management messages + - controller: initialization and de-initialization of the UB controller + - entity: enable device configuration, multi-entity management, and other + functionalities + - interrupt: implement USI interrupt functionality + - decoder: implement address decoding functionality for MMIO access to + device resource space + - resource: manage the MMIO address space allocated by the user host to + the device + - pool: implementation of pooled message processing + - instance: implement bus instance management + - ras: implement handling for RAS exceptions + - link: implement processing of link messages + - reset: implement the reset function + - hotplug: enable hot-plug functionality for the device + - sysfs: implement sysfs attribute files + - ubfi: implement parsing of the UBRT table + - bus framework: implementation of the Ubus Driver Framework + +Structure of UB device driver +============================= +In Linux, the ``ub_driver`` structure is used to describe a UB device driver. +The `struct ub_driver` is employed to represent a UB device driver, and +the structure definition is as follows. + +.. kernel-doc:: include/ub/ubus/ubus.h + :functions: ub_driver + +This structure includes a matchable device table (`id_table`), a probe function, +a remove function, a shutdown function, error handling, and other functionalities. +The following content will provide a reference for the implementation of these +features. + +Rules for Device and Driver Matching +------------------------------------ +The matching rules for UnifiedBus devices and drivers are relatively flexible, +allowing for any combination of the following five matching entries in the +`struct ub_device_id` within the device driver to achieve the target matching rule: + + - GUID's Vendor ID + - GUID's Device ID + - Configuration Space Module Vendor ID + - Configuration Space Module ID + - Configuration Space Class Code + +The ID table is an array of ``struct ub_device_id`` entries ending with an +all-zero entry. Definitions with static const are generally preferred. + +.. kernel-doc:: include/linux/mod_devicetable.h + :functions: ub_device_id + +Most drivers only need ``UB_ENTITY()`` or ``UB_ENTITY_MODULE`` or +``UB_ENTITY_CLASS()`` to set up a ub_device_id table. + +The following is an example:: + + static const struct ub_device_id sample_tbl[] = { + { 0xCC08, 0xA001, UB_ANY_ID, UB_ANY_ID, 0, 0 }, + { UB_ENTITY(0xCC08, 0xA001), 0, 0 }, + { UB_ENTITY_MODULE(0xCC08, 0xA001, 0xCC08, 0xA001), 0, 0 }, + { UB_ENTITY_CLASS(0x0200, 0xffff) }, + }; + +New UB IDs may be added to a device driver ub_ids table at runtime +as shown below:: + + echo "vendor device modulevendor moduleid class class_mask driver_data" > \ + /sys/bus/ub/drivers/sample/new_id + +All fields are passed in as hexadecimal values (no leading 0x). +The vendor and device fields are mandatory, the others are optional. Users +need pass only as many optional fields as necessary: + + - modulevendor and moduledevice fields default to UB_ANY_ID (FFFFFFFF) + - class and classmask fields default to 0 + - driver_data defaults to 0UL. + - override_only field defaults to 0. + +Note that driver_data must match the value used by any of the ub_device_id +entries defined in the driver. This makes the driver_data field mandatory +if all the ub_device_id entries have a non-zero driver_data value. + +Once added, the driver probe routine will be invoked for any unclaimed +UB devices listed in its (newly updated) ub_ids list. + +Register UB Device Driver +------------------------- +The UB device driver uses `ub_register_driver` to register the device driver. +During the registration process, the matching between the device and the +driver will be triggered, with the matching rules referenced in the previous +section. + +UB Device Driver Probe Process Reference +---------------------------------------- +- Call `ub_set_user_info` to configure the user host information into the entity + Each entity's configuration space has corresponding user register + information, such as user EID, token ID, etc. Before the device driver + starts using the device, it needs to configure the user host information + for the device. + +- Call `ub_entity_enable` to configure the access path between the host and the device + Before using the device, you need to enable the bidirectional channel + switch for accessing the device from the user host and vice versa. + This is achieved by configuring the device configuration space registers. + +- Set the DMA mask size + The device driver can reconfigure this field segment based on the + device's DMA addressing capability. The default configuration is 32-bit. + +- Call the kernel DMA interface to request DMA memory + The device driver requests DMA memory through the DMA interface provided + by the kernel to prepare for subsequent device DMA operations. + +- Call `ub_iomap` to complete the MMIO access mapping for the resource space + The device resource space stores private configurations related to device + driver capabilities. Before accessing the device resource space, you need + to call the ioremap interface to complete address mapping. The ub_iomap + interface uses the device attribute, while the ub_iomap_wc interface + uses the writecombine attribute. + +- Call `ub_alloc_irq_vectors` or `ub_alloc_irq_vectors_affinity` to complete + the interrupt request, and then call the kernel's interrupt registration API. + +- Initiate specific business functions + +UB Device Driver Removal Process Reference +------------------------------------------ +- Stop specific business functions +- Invoke the kernel's interrupt unregistration API, call ub_disable_intr, to + complete the unregistration of the interrupt handler and release the interrupt +- Call ub_iounmap to demap the MMIO access space +- Invoke the kernel's DMA interface to release DMA memory +- Call ub_entity_enable to close the access path between the host and the device +- Call ub_unset_user_info to clear the user host information configured to the + entity + +UB Device Driver Shutdown +------------------------- +The UB device shutdown is triggered during the system shutdown or restart +process, and the UB device driver needs to stop the service flow in the shutdown +interface. + +UB Device Driver Virtual configure +---------------------------------- + +If the MUE supports multiple UEs, the device driver needs to provide +`virt_configure` callback. the UEs can be enabled or disabled to facilitate +direct connection to virtual machines for use. The bus driver will cyclically +call the virt_configure callback of the device driver to enable and disable +each UE in sequence. Within the virt_configure function of the device driver, +it needs to call `ub_enable_ue` and `ub_disable_ue` provided by the bus driver +to create and destroy UEs, at the same time, private processing logic can +also be executed. + +UE can be enabled and disabled through sysfs. The process is as follows:: + + 1. Check the number of UEs currently supported by the MUE + # cat /sys/bus/ub/devices/.../ub_totalues + 2. Specify the number of enabled UEs within the maximum UE quantity range + # echo 3 > /sys/bus/ub/devices/.../ub_numues + 3. Disable UEs + # echo 0 > /sys/bus/ub/devices/.../ub_numues + +UB Device Driver Virtual notify +------------------------------- + +If the device supports multiple UEs and the MUE device driver wants to be +aware of UE state changes, `virt_notify` hook function can be implemented to +capture the UE state. + +UB Device Driver Activate and Deactivate +---------------------------------------- + +The bus driver supports maintaining the working status of entities, indicating +whether an entity is in operation. It also provides corresponding interfaces +for controlling devices to enter or exit the working state, such as +`ub_activate_entity` and `ub_deactivate_entity`. If the device driver needs +to perform any special procedures, it must implement the corresponding activate +and deactivate hook functions. + +UB Device Driver RAS handler +---------------------------- + +The bus driver provides a set of hooks for RAS processing, creating an +opportunity window to notify device drivers when handling events such as +resets and RAS, allowing them to execute corresponding processing measures. +Currently implemented hooks include `reset_prepare`, `reset_done`, +`error_detected`, and `resource_enabled`. Device drivers can optionally +provide corresponding implementations to execute their own private processing. + +Uninstall UB Device Driver +-------------------------- +The UB device driver uses `ub_unregister_driver` to unregister the driver. This +interface call will perform a remove operation on all devices matched by the +driver, ultimately removing the UB device driver from the system. + +How to find UB devices manually +=============================== + +UBUS provides several interfaces to obtain ub_entities. You can search for them +using keywords such as GUID, EID, or entity number. Or you can find an entire +class of devices using vendor ID and device ID. + +How to access UB Configuration space +==================================== + +You can use `ub_config_(read|write)_(byte|word|dword)` to access the config +space of an entity represented by `struct ub_entity *`. All these functions return +0 when successful or an error code. Most drivers expect that accesses to valid UB +entities don't fail. + +The macros for configuration space registers are defined in the header file +include/uapi/ub/ubus/ubus_regs.h. + +Vendor and device identifications +================================= + +Do not add new device or vendor IDs to include/ub/ubus/ubus_ids.h unless they +are shared across multiple drivers. You can add private definitions in +your driver if they're helpful, or just use plain hex constants. + +The device IDs are arbitrary hex numbers (vendor controlled) and normally used +only in a single location, the ub_device_id table. + +Please DO submit new vendor/device IDs to . +There's a mirror of the ub.ids file at https://gitee.com/openeuler/ubutils/ub.ids. diff --git a/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB b/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB new file mode 100644 index 0000000000000000000000000000000000000000..0a01d9140d251ace1d71ed9df41b28661370b50a --- /dev/null +++ b/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB @@ -0,0 +1 @@ +CONFIG_UB=y diff --git a/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB_UBUS b/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB_UBUS new file mode 100644 index 0000000000000000000000000000000000000000..44d0b3f6dbfbb59f0e983e8ac715fe6d29936e0e --- /dev/null +++ b/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB_UBUS @@ -0,0 +1 @@ +CONFIG_UB_UBUS=y diff --git a/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB_UBUS_BUS b/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB_UBUS_BUS new file mode 100644 index 0000000000000000000000000000000000000000..96e2bfd67afd91991beb59c52af652ad63ca7895 --- /dev/null +++ b/anolis/configs/L0-MANDATORY/arm64/CONFIG_UB_UBUS_BUS @@ -0,0 +1 @@ +CONFIG_UB_UBUS_BUS=m diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_ARM_GIC_V3_ITS_UBUS b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_ARM_GIC_V3_ITS_UBUS new file mode 100644 index 0000000000000000000000000000000000000000..1b2aa127b3aa41e63f5092ae7fb18a2d7e5006a0 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_ARM_GIC_V3_ITS_UBUS @@ -0,0 +1 @@ +CONFIG_ARM_GIC_V3_ITS_UBUS=y diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_HISI_UBUS b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_HISI_UBUS new file mode 100644 index 0000000000000000000000000000000000000000..aee9022a4efbb384bcf635b816b0f7b90c15b1f1 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_HISI_UBUS @@ -0,0 +1 @@ +CONFIG_UB_HISI_UBUS=m \ No newline at end of file diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_NET b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_NET new file mode 100644 index 0000000000000000000000000000000000000000..19f24d29ed0467438cc20c742a0455a6317a0465 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_NET @@ -0,0 +1 @@ +CONFIG_UB_NET=y diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBASE b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBASE new file mode 100644 index 0000000000000000000000000000000000000000..a7b7b9a1f12ab28c0d865bb8b7a6cc16d8b982fb --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBASE @@ -0,0 +1 @@ +CONFIG_UB_UBASE=m diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBFI b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBFI new file mode 100644 index 0000000000000000000000000000000000000000..c2daa7a5232fea4543db52549220b9f8dc1eaf30 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBFI @@ -0,0 +1 @@ +CONFIG_UB_UBFI=m diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBL b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBL new file mode 100644 index 0000000000000000000000000000000000000000..d89b5424a23135d6c5aad23b5de58f14ecbf167e --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBL @@ -0,0 +1 @@ +CONFIG_UB_UBL=m diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBRT_PLAT_DEV b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBRT_PLAT_DEV new file mode 100644 index 0000000000000000000000000000000000000000..905594d864361663f385a169dc9216e4973f5fd7 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBRT_PLAT_DEV @@ -0,0 +1 @@ +CONFIG_UB_UBRT_PLAT_DEV=y diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBUS_USI b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBUS_USI new file mode 100644 index 0000000000000000000000000000000000000000..fd03e3053c78f2175ede7209d5d80466707df556 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UBUS_USI @@ -0,0 +1 @@ +CONFIG_UB_UBUS_USI=y diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UMMU_PMU b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UMMU_PMU new file mode 100644 index 0000000000000000000000000000000000000000..f0a68ff59477672a3e1de496a5d889573469f245 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UMMU_PMU @@ -0,0 +1 @@ +CONFIG_UB_UMMU_PMU=m diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC new file mode 100644 index 0000000000000000000000000000000000000000..cffa82fd7e7087abdc7df1a60d811b4861d6cf85 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC @@ -0,0 +1 @@ +CONFIG_UB_UNIC=m diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC_DCB b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC_DCB new file mode 100644 index 0000000000000000000000000000000000000000..d5e0cf365e401dfc94d4f0e8f5383569b9ffaa89 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC_DCB @@ -0,0 +1 @@ +CONFIG_UB_UNIC_DCB=y diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC_UBL b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC_UBL new file mode 100644 index 0000000000000000000000000000000000000000..587522749782d06bcd94dde9e2d831093217bea9 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_UB_UNIC_UBL @@ -0,0 +1 @@ +CONFIG_UB_UNIC_UBL=y diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_VFIO_UB b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_VFIO_UB new file mode 100644 index 0000000000000000000000000000000000000000..81bd7c5986f857e78a244a6ad3d163c173294122 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_VFIO_UB @@ -0,0 +1 @@ +CONFIG_VFIO_UB=m diff --git a/drivers/Kconfig b/drivers/Kconfig index 572436262798a88c794b0cad0b0720b6761353d1..c77f8bfbb0204e27e67493c0ca81d058991827b4 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -7,6 +7,7 @@ source "drivers/amba/Kconfig" source "drivers/eisa/Kconfig" source "drivers/pci/Kconfig" source "drivers/cxl/Kconfig" +source "drivers/ub/Kconfig" source "drivers/pcmcia/Kconfig" source "drivers/rapidio/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 15f4df087e2efeb25570279c17fb223dd764ca36..c51310899c33c85439906d17daf4b7c4218a659d 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_GPIOLIB) += gpio/ obj-y += pwm/ obj-y += pci/ +obj-$(CONFIG_UB) += ub/ obj-$(CONFIG_PARISC) += parisc/ obj-$(CONFIG_RAPIDIO) += rapidio/ diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 3b921658bb00d1dca0049a4b6da27980c142f299..a8ce2696ff24930be6985c754096ab5a651f03fe 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -48,7 +48,7 @@ #include #include #include - +#include #include #include #include @@ -682,6 +682,18 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) #endif } +static void ghes_handle_ubus_err(struct acpi_hest_generic_data *gdata) +{ + struct cper_sec_ubus *ubus_err = acpi_hest_get_payload(gdata); + ub_ras_recover_func_t ras_recover_func; + + ras_recover_func = ub_ras_get_recover_func(); + if (ras_recover_func) + ras_recover_func(ubus_err, gdata->error_severity); + else + pr_warn(GHES_PFX "UBUS error occurs when the ras driver is not loaded.\n"); +} + static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list); int ghes_register_vendor_record_notifier(struct notifier_block *nb) @@ -866,8 +878,9 @@ static void ghes_do_proc(struct ghes *ghes, arch_apei_report_pcie_error(sec_sev, pcie_err); ghes_handle_aer(gdata); - } - else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { + } else if (guid_equal(sec_type, &CPER_SEC_UBUS)) { + ghes_handle_ubus_err(gdata); + } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { queued = ghes_handle_arm_hw_error(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index a1dff2b166f046f195a23cef035f49722d88041e..da4cd3cef36eb268d4bfab1b15789a8b23e91a74 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -102,6 +102,13 @@ config ARM_GIC_V3_ITS_PCI depends on PCI_MSI default ARM_GIC_V3_ITS +config ARM_GIC_V3_ITS_UBUS + bool + depends on ARM_GIC_V3_ITS + depends on UB_UBUS + depends on UB_UBUS_USI + default ARM_GIC_V3_ITS + config ARM_GIC_V3_ITS_FSL_MC bool depends on ARM_GIC_V3_ITS diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index a9f8fe849f8e1d462c1be034d312225657189652..b58fe6bcdf2421f9cd3a7956db1007944b9a52b9 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o +obj-$(CONFIG_ARM_GIC_V3_ITS_UBUS) += irq-gic-v3-its-ub-msi.o obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o obj-$(CONFIG_ARM_GIC_PHYTIUM_2500) += irq-gic-phytium-2500.o irq-gic-phytium-2500-its.o obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o diff --git a/drivers/irqchip/irq-gic-v3-its-ub-msi.c b/drivers/irqchip/irq-gic-v3-its-ub-msi.c new file mode 100644 index 0000000000000000000000000000000000000000..08274a57c5d5960625ba874a0d4c7c7e6c20dfba --- /dev/null +++ b/drivers/irqchip/irq-gic-v3-its-ub-msi.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void its_mask_msi_irq(struct irq_data *d) +{ + ub_msi_mask_irq(d); + irq_chip_mask_parent(d); +} + +static void its_unmask_msi_irq(struct irq_data *d) +{ + ub_msi_unmask_irq(d); + irq_chip_unmask_parent(d); +} + +static struct irq_chip its_msi_irq_chip = { + .name = "ITS-MSI", + .irq_unmask = its_unmask_msi_irq, + .irq_mask = its_mask_msi_irq, + .irq_eoi = irq_chip_eoi_parent, +}; + +static int its_ub_msi_prepare(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *info) +{ + struct msi_domain_info *msi_info; + int alias_count = 0, minnvec = 1; + struct ub_entity *uent = to_ub_entity(dev); + int cnt, ret; + + msi_info = msi_get_domain_info(domain->parent); + + ret = ub_interrupt_id_alloc(uent); + if (ret) { + dev_err(&uent->dev, "device id alloc failed, ret = %d.\n", ret); + return ret; + } + + info->scratchpad[0].ul = uent->intr_device_id; + dev_info(&uent->dev, "device id alloc success, id: %u.\n", uent->intr_device_id); + ub_write_interruptid(uent); + + cnt = max(nvec, alias_count); + cnt = max_t(int, minnvec, roundup_pow_of_two((u32)cnt)); + + return msi_info->ops->msi_prepare(domain->parent, dev, cnt, info); +} + +static struct msi_domain_ops its_ub_msi_ops = { + .msi_prepare = its_ub_msi_prepare, +}; + +static struct msi_domain_info its_ub_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_UB_INTR), + .ops = &its_ub_msi_ops, + .chip = &its_msi_irq_chip, +}; + +static int its_ub_msi_init_one(struct fwnode_handle *handle, + const char *name) +{ + struct irq_domain *parent; + + parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS); + if (!parent || !msi_get_domain_info(parent)) { + pr_err("%s: Unable to locate ITS domain\n", name); + return -ENXIO; + } + + if (!ub_msi_create_irq_domain(handle, &its_ub_msi_domain_info, parent)) { + pr_err("%s: Unable to create UB domain\n", name); + return -ENOMEM; + } + + return 0; +} + +static int its_ub_msi_parse_madt(union acpi_subtable_headers *header, + const unsigned long end) +{ + struct acpi_madt_generic_translator *its_entry; + struct fwnode_handle *dom_handle; + char *node_name; + int err = -ENXIO; + + its_entry = (struct acpi_madt_generic_translator *)header; + node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", + (long)its_entry->base_address); + + dom_handle = iort_find_domain_token(its_entry->translation_id); + if (!dom_handle) { + pr_err("%s: Unable to locate ITS domain handle\n", node_name); + goto out; + } + + err = its_ub_msi_init_one(dom_handle, (const char *)node_name); + if (!err) + pr_info("UB/USI: %s domain created\n", node_name); +out: + kfree(node_name); + return err; +} + +static int its_ub_acpi_msi_init(void) +{ + acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, + its_ub_msi_parse_madt, 0); + return 0; +} + +static const struct of_device_id its_device_id[] = { + { .compatible = "arm,gic-v3-its", }, + {}, +}; + +static int its_ub_of_msi_init(void) +{ + struct device_node *np; + + for (np = of_find_matching_node(NULL, its_device_id); np; + np = of_find_matching_node(np, its_device_id)) { + if (!of_device_is_available(np)) + continue; + if (!of_property_read_bool(np, "msi-controller")) + continue; + + if (its_ub_msi_init_one(of_node_to_fwnode(np), np->full_name)) + continue; + + pr_info("UB/USI: %pOF domain created\n", np); + } + + return 0; +} + +static int __init its_ub_msi_init(void) +{ + its_ub_of_msi_init(); + its_ub_acpi_msi_init(); + + return 0; +} +early_initcall(its_ub_msi_init); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 8aa6eccb56be1eb8989fb302810b10b374ad145d..c803c39793f3c2c1c039bc7f8ac760bd53982e0d 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -494,6 +494,8 @@ source "drivers/net/fddi/Kconfig" source "drivers/net/hippi/Kconfig" +source "drivers/net/ub/Kconfig" + source "drivers/net/ipa/Kconfig" config NET_SB1000 diff --git a/drivers/net/Makefile b/drivers/net/Makefile index e15939e77122b922004ad98739cd9b943c053708..71b8dfe43a39238738ec888d73200dd9e86ef7ed 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_WLAN) += wireless/ obj-$(CONFIG_IEEE802154) += ieee802154/ obj-$(CONFIG_WWAN) += wwan/ obj-$(CONFIG_MCTP) += mctp/ +obj-$(CONFIG_UB_NET) += ub/ obj-$(CONFIG_VMXNET3) += vmxnet3/ obj-$(CONFIG_XEN_NETDEV_FRONTEND) += xen-netfront.o diff --git a/drivers/net/ub/Kconfig b/drivers/net/ub/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..f74de48436559fccd0c4295be8832000d2cc00bb --- /dev/null +++ b/drivers/net/ub/Kconfig @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# UBL configuration +# + +menuconfig UB_NET + bool "UB network driver support" + depends on NET + depends on ARM64 + default n + help + If you have a network (UB) card belonging to this class, say Y. + + UB is a new interconnection protocol that defines its own physical layer, + data link layer, and network layer. + This option includes the data link layer and all UB network device drivers. + On the otherside, it can also be used with ethernet link for layer 2, + which still integrates with the TCP/IP protocol stack. + +if UB_NET + +config UB_UBL + default n + tristate "UB link support" + help + This enables UB link support which is + used to replace traditional ethernet layer. + Say 'Y' or 'm' if your device works + in UB mode. + +config UB_UNIC + tristate "NIC based on UB devices" + default n + depends on UB_UBASE + help + This option is used to enable the networking capabilities of the + UB device and integrate with the TCP/IP protocol stack. + This module depends upon UB_UBASE driver to access the ub devices + and their associated operations. For UB devices, in addition to the + ubase driver, it also relies on the ubl driver. + +config UB_UNIC_UBL + bool "Hisilicon UNIC UB link support" + default n + depends on UB_UBL + depends on UB_UNIC + help + This option enables support for UB Link (UBL) in the Hisilicon + UNIC network driver. UB link is used to replace traditional + ethernet layer in the UB devices. When enable UB_UNIC_UBL config, + UNIC driver will use the interfaces defined by UBL. + + To use this feature, you must have both the UB_UBL and UB_UNIC + options enabled. + + If you are unsure whether you need this functionality, it is + recommended to leave it disabled to avoid unnecessary kernel bloat. + +config UB_UNIC_DCB + bool "Hisilicon UNIC Data Center Bridge Support" + default n + depends on DCB + depends on UB_UNIC + help + This option enables support for Data Center Bridging (DCB) in the + Hisilicon UNIC network driver. DCB allows for the configuration of + network traffic classes and priorities, enhancing network performance + in data center environments. + + To use this feature, you must have both the DCB and UB_UNIC options enabled. + + If you are unsure whether you need this functionality, it is + recommended to leave it disabled to avoid unnecessary kernel bloat. + +endif #UB_NET diff --git a/drivers/net/ub/Makefile b/drivers/net/ub/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..955b495f6fe2420b53dbaeb68fa0c08f7f7c67f6 --- /dev/null +++ b/drivers/net/ub/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile for the HISILICON network device drivers. +# + +#### compile ubl +obj-$(CONFIG_UB_UBL) += dev/ +obj-$(CONFIG_UB_UNIC) += unic/ \ No newline at end of file diff --git a/drivers/net/ub/dev/Makefile b/drivers/net/ub/dev/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f7130b0da68ee04ad1d4173f42864fa1cce8b374 --- /dev/null +++ b/drivers/net/ub/dev/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile for the HISILICON network device drivers. +# + +#### compile ubl +obj-$(CONFIG_UB_UBL) += ubl.o \ No newline at end of file diff --git a/drivers/net/ub/dev/ubl.c b/drivers/net/ub/dev/ubl.c new file mode 100644 index 0000000000000000000000000000000000000000..b3637476b1fbd154c47a238f1dbffcc1cab938fc --- /dev/null +++ b/drivers/net/ub/dev/ubl.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + * 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. + * + * UBL An implementation of the UB protocol suite for the LINUX + * operating system. + * + * UB link device handling. + * + * Version: @(#)ubl.c 1.0.1 25/09/24 + * + */ + +#include +#include +#include + +static __be16 ubl_type_to_proto(u8 type) +{ + __be16 proto; + + switch (type) { + case UB_IPV4_CFG_TYPE: + proto = htons(ETH_P_IP); + break; + case UB_IPV6_CFG_TYPE: + proto = htons(ETH_P_IPV6); + break; + case UB_NOIP_CFG_TYPE: + default: + proto = htons(ETH_P_UB); + break; + } + + return proto; +} + +/** + * ubl_add_sw_ctype - add software packet type for skb->data + * @skb: buffer to alter + * @ctype: indicates the packet type + * + * The packet type cannot be known by parsing packets from user, + * which leads to restrictions on the use of socket. + * Add sw_ctype field to indicate the packet type. And sw_ctype + * exists only during software prcessing. + * +----------+----+-----+-----------+ + * | sw_ctype | CC | NPI | L3 Packet | + * +----------+----+-----+-----------+ + */ +static int ubl_add_sw_ctype(struct sk_buff *skb, u8 ctype) +{ + u8 *pkt_cfg; + + if (skb_cow_head(skb, sizeof(u8))) + return -ENOMEM; + + pkt_cfg = (u8 *)skb_push(skb, sizeof(u8)); + *pkt_cfg = ctype; + + return 0; +} + +/** + * ubl_create_header - create the ubl header + * @skb: buffer to alter + * @dev: source device + * @type: ubl type field + * @daddr: not used in ubl + * @saddr: not used in ubl + * @len: packet length (<= skb->len) + * + */ +static int ubl_create_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned int len) +{ + struct ublhdr *ubl; + + if (type == ETH_P_IP || type == ETH_P_IPV6) { + ubl = (struct ublhdr *)skb_push(skb, UBL_HLEN); + memset(ubl, 0, sizeof(struct ublhdr)); + ubl->h_npi = htonl(UB_DEFAULT_NPI); + ubl->cfg = (type == ETH_P_IP) ? UB_IPV4_CFG_TYPE : UB_IPV6_CFG_TYPE; + return UBL_HLEN; + } else if (type == ETH_P_UB) { + return 0; + } + + return -UBL_HLEN; +} + +/** + * ubl_header_parse_protocol - parse packets protocol before sending it to + * driver. + * @skb: buffer to alter + * + * parse packets based on packet data if skb->protocol is ETH_P_ALL or 0. + */ +static __be16 ubl_header_parse_protocol(const struct sk_buff *skb) +{ + return ubl_type_to_proto(skb->data[0]); +} + +static const struct header_ops ubl_header_ops ____cacheline_aligned = { + .create = ubl_create_header, + .parse_protocol = ubl_header_parse_protocol, +}; + +/** + * ubl_setup - setup ub link network device + * @dev: network device + * + * Fill in the fields of the device structure with ubl-generic values. + */ +void ubl_setup(struct net_device *dev) +{ + dev->header_ops = &ubl_header_ops; + dev->type = ARPHRD_UB; + dev->hard_header_len = UBL_HLEN; + dev->min_header_len = UBL_HLEN; + dev->mtu = UB_DATA_LEN; + dev->min_mtu = UB_MIN_MTU; + dev->max_mtu = UB_DATA_LEN; + dev->addr_len = UBL_ALEN; + dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; + dev->flags = (IFF_NOARP | IFF_POINTOPOINT); + dev->priv_flags |= IFF_TX_SKB_SHARING; +} +EXPORT_SYMBOL(ubl_setup); + +/** + * alloc_ubldev_mqs - Allocates and sets up a ub-link device + * @sizeof_priv: Size of additional driver-private structure to be allocated + * for this ubl device + * @txqs: The number of TX queues this device has. + * @rxqs: The number of RX queues this device has. + * + * Fill in the fields of the device structure with ubl-generic + * values. Basically done everything except registering the device. + * + * Constructs a new net device, completing with a private data area of + * size (sizeof_priv). A 32-byte (not bit) alignment is enforced for + * this private data area. + */ + +struct net_device *alloc_ubldev_mqs(int sizeof_priv, unsigned int txqs, + unsigned int rxqs) +{ + return alloc_netdev_mqs(sizeof_priv, "ubl%d", NET_NAME_UNKNOWN, + ubl_setup, txqs, rxqs); +} +EXPORT_SYMBOL(alloc_ubldev_mqs); + +/** + * ubl_type_trans - obtains skb->protocol and adds sw_ptype to the packet + * @skb: buffer to alter + * @dev: source device + * @type: packet type + * + * Obtains the packet type and translates it to skb->protocol and adds sw_ptype + * to the packet data. + */ +__be16 ubl_type_trans(struct sk_buff *skb, struct net_device *dev, u8 type) +{ + skb->dev = dev; + if (ubl_add_sw_ctype(skb, type)) + net_warn_ratelimited("add sw ctype failed\n"); + + skb_reset_mac_header(skb); + if (type == UB_IPV4_CFG_TYPE || type == UB_IPV6_CFG_TYPE) + skb_pull_inline(skb, UBL_HLEN); + else if (type != UB_NOIP_CFG_TYPE) + net_warn_ratelimited("An unknown packet is received by %s, type is %u\n", + dev->name, type); + + return ubl_type_to_proto(type); +} +EXPORT_SYMBOL(ubl_type_trans); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("UB link level"); +MODULE_VERSION(UBL_MOD_VERSION); diff --git a/drivers/net/ub/unic/Makefile b/drivers/net/ub/unic/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..1419ee5695958226ceae194ceedf552dd3d7744f --- /dev/null +++ b/drivers/net/ub/unic/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile for the HISILICON network device drivers. +# + +ccflags-y += -I$(srctree)/$(src) +ccflags-y += -I$(srctree)/drivers/net/ub/unic/debugfs + +obj-$(CONFIG_UB_UNIC) += unic.o +unic-objs = unic_main.o unic_ethtool.o unic_hw.o unic_guid.o unic_netdev.o unic_dev.o unic_qos_hw.o unic_event.o unic_crq.o +unic-objs += unic_channel.o debugfs/unic_debugfs.o unic_rx.o unic_tx.o unic_txrx.o unic_comm_addr.o unic_rack_ip.o unic_stats.o +unic-$(CONFIG_UB_UNIC_DCB) += unic_dcbnl.o diff --git a/drivers/net/ub/unic/debugfs/unic_debugfs.c b/drivers/net/ub/unic/debugfs/unic_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..112e64c25a3487002dde49aa5454315f20bd223a --- /dev/null +++ b/drivers/net/ub/unic/debugfs/unic_debugfs.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include +#include +#include + +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_debugfs.h" + +static int unic_dbg_dump_dev_info(struct seq_file *s, void *data) +{ + struct unic_dev *unic_dev = dev_get_drvdata(s->private); + struct net_device *netdev = unic_dev->comdev.netdev; + u16 max_frame_size; + int ret; + + seq_printf(s, "%-25s", "DEV_NAME:"); + seq_printf(s, "%-12s\n", netdev->name); + + if (!unic_dev_ubl_supported(unic_dev)) { + ret = unic_check_validate_dump_mtu(unic_dev, netdev->mtu, + &max_frame_size); + if (!ret) { + seq_printf(s, "%-25s", "MAX_FRAME_SIZE:"); + seq_printf(s, "%-12u\n", max_frame_size); + } + } + + return 0; +} + +static const struct unic_dbg_cap_bit_info { + const char *format; + bool (*get_bit)(struct unic_dev *dev); +} unic_cap_bits[] = { + {"\tsupport_ubl: %u\n", &unic_dev_ubl_supported}, + {"\tsupport_ets: %u\n", &unic_dev_ets_supported}, + {"\tsupport_fec: %u\n", &unic_dev_fec_supported}, + {"\tsupport_rss: %u\n", &unic_dev_rss_supported}, + {"\tsupport_tc_speed_limit: %u\n", &unic_dev_tc_speed_limit_supported}, + {"\tsupport_tx_csum_offload: %u\n", &unic_dev_tx_csum_offload_supported}, + {"\tsupport_rx_csum_offload: %u\n", &unic_dev_rx_csum_offload_supported}, + {"\tsupport_fec_stats: %u\n", &unic_dev_fec_stats_supported}, +}; + +static void unic_dbg_dump_caps_bits(struct unic_dev *unic_dev, + struct seq_file *s) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(unic_cap_bits); i++) + seq_printf(s, unic_cap_bits[i].format, + unic_cap_bits[i].get_bit(unic_dev)); +} + +static void unic_dbg_dump_caps(struct unic_dev *unic_dev, struct seq_file *s) +{ + struct unic_caps *unic_caps = &unic_dev->caps; + struct unic_dbg_caps_info { + const char *format; + u32 caps_info; + } unic_caps_info[] = { + {"\ttotal_ip_tbl_size: %hu\n", unic_caps->total_ip_tbl_size}, + {"\tmax_trans_unit: %hu\n", unic_caps->max_trans_unit}, + {"\tmin_trans_unit: %hu\n", unic_caps->min_trans_unit}, + {"\tvport_buf_size: %u\n", unic_caps->vport_buf_size}, + {"\tvport_buf_num: %hhu\n", unic_caps->vport_buf_num}, + {"\tmax_int_ql: %hu\n", unic_caps->max_int_ql}, + {"\tmax_int_gl: %hu\n", unic_caps->max_int_gl}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(unic_caps_info); i++) + seq_printf(s, unic_caps_info[i].format, + unic_caps_info[i].caps_info); +} + +static int unic_dbg_dump_caps_info(struct seq_file *s, void *data) +{ + struct unic_dev *unic_dev = dev_get_drvdata(s->private); + + seq_puts(s, "CAP_BITS:\n"); + unic_dbg_dump_caps_bits(unic_dev, s); + seq_puts(s, "\nCAPS:\n"); + unic_dbg_dump_caps(unic_dev, s); + + return 0; +} + +static void unic_dump_page_pool_info(struct page_pool *page_pool, + struct seq_file *s, u32 index) +{ +#define UNIC_SIZE_1K 1024 + + seq_printf(s, "%-10u", index); + seq_printf(s, "%-14u", READ_ONCE(page_pool->pages_state_hold_cnt)); + seq_printf(s, "%-14d", atomic_read(&page_pool->pages_state_release_cnt)); + seq_printf(s, "%-21u", page_pool->p.pool_size); + seq_printf(s, "%-7u", page_pool->p.order); + seq_printf(s, "%-9d", page_pool->p.nid); + seq_printf(s, "%uK\n", page_pool->p.max_len / UNIC_SIZE_1K); +} + +static int unic_dbg_dump_page_pool_info(struct seq_file *s, void *data) +{ + struct unic_dev *unic_dev = dev_get_drvdata(s->private); + struct unic_rq *rq; + int ret = 0; + u32 i; + + seq_puts(s, "QUEUE_ID ALLOCATE_CNT FREE_CNT POOL_SIZE(PAGE_NUM) "); + seq_puts(s, "ORDER NUMA_ID MAX_LEN\n"); + + if (!mutex_trylock(&unic_dev->channels.mutex)) + return -EBUSY; + + if (__unic_resetting(unic_dev) || + !unic_dev->channels.c) { + ret = -EBUSY; + goto out; + } + + for (i = 0; i < unic_dev->channels.num; i++) { + rq = unic_dev->channels.c[i].rq; + unic_dump_page_pool_info(rq->page_pool, s, i); + } + +out: + mutex_unlock(&unic_dev->channels.mutex); + return ret; +} + +static int unic_dbg_dump_rss_cfg_hw(struct seq_file *s, void *data) +{ +#define UNIC_RSS_CFG_ITEMS_NUM 6 + + struct unic_dev *unic_dev = dev_get_drvdata(s->private); + struct unic_cfg_rss_cmd resp = {0}; + u16 jfr_cnt; + u16 i = 0; + int ret; + + if (__unic_resetting(unic_dev)) + return -EBUSY; + + seq_puts(s, "TC_VAILD TC_MODE JFR_0 JFR_1 JFR_2 JFR_3\n"); + + ret = unic_query_rss_cfg(unic_dev, &resp); + if (ret) + return ret; + + seq_printf(s, "%-10u", resp.tc_vaild); + seq_printf(s, "%-9u", resp.tc_mode); + jfr_cnt = min(le16_to_cpu(resp.jfr_reg_num), UNIC_RSS_MAX_CNT); + jfr_cnt = min(UNIC_RSS_CFG_ITEMS_NUM, jfr_cnt); + for (i = 0; i < jfr_cnt; i++) + seq_printf(s, "%-7u", le16_to_cpu(resp.jfr_idx[i])); + seq_puts(s, "\n"); + + return 0; +} + +static int unic_dbg_dump_promisc_cfg_hw(struct seq_file *s, void *data) +{ + struct unic_dev *unic_dev = dev_get_drvdata(s->private); + struct unic_promisc_cfg_cmd resp = {0}; + int ret; + + if (__unic_resetting(unic_dev)) + return -EBUSY; + + ret = unic_get_promisc_mode(unic_dev, &resp); + if (ret) + return ret; + + seq_printf(s, "rx_uc_ip_en : %-2u\n", resp.promisc_rx_uc_ip_en); + seq_printf(s, "rx_uc_guid_en : %-2u\n", resp.promisc_rx_uc_guid_en); + seq_printf(s, "rx_mc_en : %-2u\n", resp.promisc_rx_mc_en); + seq_printf(s, "rx_uc_mac_en : %-2u\n", resp.promisc_rx_uc_mac_en); + seq_printf(s, "rx_mc_mac_en : %-2u\n", resp.promisc_rx_mc_mac_en); + seq_printf(s, "rx_bc_en : %-2u\n", resp.promisc_rx_bc_en); + + return 0; +} + +static bool unic_dbg_dentry_support(struct device *dev, u32 property) +{ + struct unic_dev *unic_dev = dev_get_drvdata(dev); + + return ubase_dbg_dentry_support(unic_dev->comdev.adev, property); +} + +static struct ubase_dbg_dentry_info unic_dbg_dentry[] = { + /* keep unic at the bottom and add new directory above */ + { + .name = "unic", + .property = UBASE_SUP_UNIC | UBASE_SUP_UBL, + .support = unic_dbg_dentry_support, + }, +}; + +static struct ubase_dbg_cmd_info unic_dbg_cmd[] = { + { + .name = "dev_info", + .dentry_index = UNIC_DBG_DENTRY_ROOT, + .property = UBASE_SUP_UNIC | UBASE_SUP_UBL, + .support = unic_dbg_dentry_support, + .init = ubase_dbg_seq_file_init, + .read_func = unic_dbg_dump_dev_info, + }, { + .name = "caps_info", + .dentry_index = UNIC_DBG_DENTRY_ROOT, + .property = UBASE_SUP_UNIC | UBASE_SUP_UBL, + .support = unic_dbg_dentry_support, + .init = ubase_dbg_seq_file_init, + .read_func = unic_dbg_dump_caps_info, + }, { + .name = "page_pool_info", + .dentry_index = UNIC_DBG_DENTRY_ROOT, + .property = UBASE_SUP_UNIC | UBASE_SUP_UBL, + .support = unic_dbg_dentry_support, + .init = ubase_dbg_seq_file_init, + .read_func = unic_dbg_dump_page_pool_info, + }, { + .name = "rss_cfg_hw", + .dentry_index = UNIC_DBG_DENTRY_ROOT, + .property = UBASE_SUP_UNIC | UBASE_SUP_UBL, + .support = unic_dbg_dentry_support, + .init = ubase_dbg_seq_file_init, + .read_func = unic_dbg_dump_rss_cfg_hw, + }, { + .name = "promisc_cfg_hw", + .dentry_index = UNIC_DBG_DENTRY_ROOT, + .property = UBASE_SUP_UNIC | UBASE_SUP_UBL, + .support = unic_dbg_dentry_support, + .init = ubase_dbg_seq_file_init, + .read_func = unic_dbg_dump_promisc_cfg_hw, + } +}; + +int unic_dbg_init(struct auxiliary_device *adev) +{ + struct dentry *ubase_root_dentry = unic_get_ubase_root_dentry(adev); + struct ubase_dbg_dentry_info dentry[UNIC_DBG_DENTRY_ROOT + 1] = {0}; + u8 dentry_num = ARRAY_SIZE(unic_dbg_dentry); + struct device *dev = &adev->dev; + struct unic_dev *unic_dev; + int ret; + + unic_dev = (struct unic_dev *)dev_get_drvdata(dev); + + if (!ubase_root_dentry) { + unic_err(unic_dev, "dbgfs root dentry does not exist.\n"); + return -ENOENT; + } + + unic_dev->dbgfs.dentry = debugfs_create_dir(unic_dbg_dentry[dentry_num - 1].name, + ubase_root_dentry); + if (IS_ERR(unic_dev->dbgfs.dentry)) { + unic_err(unic_dev, "failed to create unic debugfs root dir.\n"); + return PTR_ERR(unic_dev->dbgfs.dentry); + } + + memcpy(dentry, unic_dbg_dentry, sizeof(dentry)); + dentry[UNIC_DBG_DENTRY_ROOT].dentry = unic_dev->dbgfs.dentry; + unic_dev->dbgfs.cmd_info = unic_dbg_cmd; + unic_dev->dbgfs.cmd_info_size = ARRAY_SIZE(unic_dbg_cmd); + + ret = ubase_dbg_create_dentry(dev, &unic_dev->dbgfs, dentry, + ARRAY_SIZE(dentry) - 1); + if (ret) { + unic_err(unic_dev, + "failed to create unic debugfs dentry, ret = %d.\n", + ret); + goto create_dentry_err; + } + + return 0; + +create_dentry_err: + debugfs_remove_recursive(unic_dev->dbgfs.dentry); + + return ret; +} + +void unic_dbg_uninit(struct auxiliary_device *adev) +{ + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + + debugfs_remove_recursive(unic_dev->dbgfs.dentry); +} diff --git a/drivers/net/ub/unic/debugfs/unic_debugfs.h b/drivers/net/ub/unic/debugfs/unic_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..6efd739d2c5b303e3ba40d27687abe8390b833b0 --- /dev/null +++ b/drivers/net/ub/unic/debugfs/unic_debugfs.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_DEBUGFS_H__ +#define __UNIC_DEBUGFS_H__ + +#include +#include + +#define unic_get_ubase_root_dentry(adev) ubase_diag_debugfs_root(adev) + +enum unic_dbg_dentry_type { + /* must be the last entry. */ + UNIC_DBG_DENTRY_ROOT +}; + +int unic_dbg_init(struct auxiliary_device *adev); +void unic_dbg_uninit(struct auxiliary_device *adev); + +#endif /* __UNIC_DEBUGFS_H__ */ diff --git a/drivers/net/ub/unic/unic.h b/drivers/net/ub/unic/unic.h new file mode 100644 index 0000000000000000000000000000000000000000..7f6572c50a0c859fa78867d5b8724bbbc9b8af32 --- /dev/null +++ b/drivers/net/ub/unic/unic.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_H__ +#define __UNIC_H__ + +#include +#include +#include +#include + +#define UNIC_CAP_LEN 3 +#define UNIC_MAX_VPORT_BUF_NUM 6 + +enum { + UNIC_SUPPORT_PFC_B = 0, + UNIC_SUPPORT_ETS_B = 1, + UNIC_SUPPORT_FEC_B = 2, + UNIC_SUPPORT_PAUSE_B = 3, + UNIC_SUPPORT_GRO_B = 5, + UNIC_SUPPORT_ETH_B = 7, + + UNIC_SUPPORT_TSO_B = 8, + UNIC_SUPPORT_RSS_B = 9, + UNIC_SUPPORT_SERIAL_SERDES_LB_B = 10, + UNIC_SUPPORT_TC_SPEED_LIMIT_B = 12, + UNIC_SUPPORT_TX_CSUM_OFFLOAD_B = 13, + UNIC_SUPPORT_TUNNEL_CSUM_OFFLOAD_B = 14, + UNIC_SUPPORT_PTP_B = 15, + + UNIC_SUPPORT_RX_CSUM_OFFLOAD_B = 16, + UNIC_SUPPORT_APP_LB_B = 17, + UNIC_SUPPORT_FEC_STATS_B = 21, + UNIC_SUPPORT_EXTERNAL_LB_B = 23, + + UNIC_SUPPORT_PARALLEL_SERDES_LB_B = 24, + UNIC_SUPPORT_CFG_VLAN_FILTER_B = 26, + UNIC_SUPPORT_CFG_MAC_B = 27, + + /* must be last entry and it should <= UNIC_CAP_LEN * 32 */ + UNIC_SUPPORT_MASK_NBITS +}; + +#define unic_get_cap_bit(unic_dev, nr) \ + test_bit(nr, (unsigned long *)((unic_dev)->cap_bits)) + +#define UNIC_USER_UPE BIT(0) /* unicast promisc enabled by user */ +#define UNIC_USER_MPE BIT(1) /* mulitcast promisc enabled by user */ +#define UNIC_USER_BPE BIT(2) /* broadcast promisc enabled by user */ +#define UNIC_OVERFLOW_MGP BIT(3) /* mulitcast guid overflow */ +#define UNIC_OVERFLOW_IPP BIT(4) /* unicast ip overflow */ +#define UNIC_UPE (UNIC_USER_UPE | \ + UNIC_OVERFLOW_IPP) +#define UNIC_MPE (UNIC_USER_MPE | \ + UNIC_OVERFLOW_MGP) + +#define UNIC_RSS_MAX_VL_NUM UBASE_NIC_MAX_VL_NUM +#define UNIC_INVALID_PRIORITY (0xff) +#define UNIC_MAX_PRIO_NUM IEEE_8021QAZ_MAX_TCS +#define UNIC_VL_TSA_DWRR IEEE_8021QAZ_TSA_ETS + +/* must be consistent with definition in firmware */ +enum unic_media_type { + UNIC_MEDIA_TYPE_UNKNOWN, + UNIC_MEDIA_TYPE_FIBER, + UNIC_MEDIA_TYPE_BACKPLANE, + UNIC_MEDIA_TYPE_NONE, +}; + +/* must be consistent with definition in firmware */ +enum unic_module_type { + UNIC_MODULE_TYPE_UNKNOWN = 0x00, + UNIC_MODULE_TYPE_FIBRE_LR = 0x01, + UNIC_MODULE_TYPE_FIBRE_SR = 0x02, + UNIC_MODULE_TYPE_AOC = 0x03, + UNIC_MODULE_TYPE_CR = 0x04, + UNIC_MODULE_TYPE_KR = 0x05, + UNIC_MODULE_TYPE_TP = 0x06, +}; + +#define UNIC_LANES_1 1 +#define UNIC_LANES_2 2 +#define UNIC_LANES_4 4 +#define UNIC_LANES_8 8 + +#define UNIC_SUPPORT_200G_X2_BIT BIT(0) +#define UNIC_SUPPORT_200G_X4_BIT BIT(1) +#define UNIC_SUPPORT_400G_X4_BIT BIT(2) +#define UNIC_SUPPORT_400G_X8_BIT BIT(3) +#define UNIC_SUPPORT_25G_X1_BIT BIT(4) +#define UNIC_SUPPORT_50G_X1_BIT BIT(5) +#define UNIC_SUPPORT_50G_X2_BIT BIT(6) +#define UNIC_SUPPORT_100G_X1_BIT BIT(7) +#define UNIC_SUPPORT_100G_X2_BIT BIT(8) +#define UNIC_SUPPORT_100G_X4_BIT BIT(9) +#define UNIC_SUPPORT_10G_X1_BIT BIT(10) + +enum unic_mac_speed { + UNIC_MAC_SPEED_UNKNOWN = 0, + UNIC_MAC_SPEED_10G = SPEED_10000, /* 10000 Mbps = 10 Gbps */ + UNIC_MAC_SPEED_25G = SPEED_25000, /* 25000 Mbps = 25 Gbps */ + UNIC_MAC_SPEED_50G = SPEED_50000, /* 50000 Mbps = 50 Gbps */ + UNIC_MAC_SPEED_100G = SPEED_100000, /* 100000 Mbps = 100 Gbps */ + UNIC_MAC_SPEED_200G = SPEED_200000, /* 200000 Mbps = 200 Gbps */ + UNIC_MAC_SPEED_400G = SPEED_400000, /* 400000 Mbps = 400 Gbps */ +}; + +#define UNIC_MBYTE_PER_SEND 125000 + +#endif diff --git a/drivers/net/ub/unic/unic_channel.c b/drivers/net/ub/unic/unic_channel.c new file mode 100644 index 0000000000000000000000000000000000000000..eb2d838035c7879aec1c1a55bd4f6e77dc7dffc6 --- /dev/null +++ b/drivers/net/ub/unic/unic_channel.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include "unic_channel.h" +#include "unic_dev.h" +#include "unic_netdev.h" + +void unic_get_channels(struct net_device *ndev, + struct ethtool_channels *ch) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + + ch->max_combined = unic_channels_max_num(unic_dev->comdev.adev); + ch->combined_count = unic_dev->channels.rss_size; +} + +static int unic_check_rss_size_param(struct unic_dev *unic_dev, u32 new_rss_size, + u32 org_rss_size) +{ + u32 max_rss_size; + + if (org_rss_size == new_rss_size) { + unic_err(unic_dev, + "old num and new num are the same, rss_size = %u.\n", + org_rss_size); + return -EINVAL; + } + + max_rss_size = unic_get_max_rss_size(unic_dev); + if (new_rss_size < 1 || new_rss_size > max_rss_size) { + unic_err(unic_dev, + "the rss_size(%u) is out of the range [1, %u].\n", + new_rss_size, max_rss_size); + return -EINVAL; + } + + if (max_rss_size % new_rss_size) { + unic_err(unic_dev, + "the rss_size(%u) can't distributed to max_rss_size(%u).\n", + new_rss_size, max_rss_size); + return -EINVAL; + } + + return 0; +} + +static int unic_check_set_channels_available(struct net_device *ndev) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + + if (netif_running(ndev)) { + unic_err(unic_dev, "failed to set channels, due to network interface is up, please down it first and try again.\n"); + return -EBUSY; + } + + if (unic_resetting(ndev)) { + unic_err(unic_dev, + "failed to set channels, due to dev resetting.\n"); + return -EBUSY; + } + + return 0; +} + +int unic_set_channels(struct net_device *ndev, + struct ethtool_channels *ch) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + u32 org_rss_size = unic_dev->channels.rss_size; + u32 new_rss_size = ch->combined_count; + int ret, ret1; + + ret = unic_check_set_channels_available(ndev); + if (ret) + return ret; + + ret = unic_check_rss_size_param(unic_dev, new_rss_size, org_rss_size); + if (ret) + return ret; + + ret = unic_change_rss_size(unic_dev, new_rss_size, org_rss_size); + if (ret) { + unic_err(unic_dev, + "failed to change rss_size, revert to old rss_size, ret = %d.\n", + ret); + + ret1 = unic_change_rss_size(unic_dev, org_rss_size, org_rss_size); + if (ret1) { + unic_err(unic_dev, + "failed to revert to old rss_size, ret1 = %d.\n", + ret1); + return ret1; + } + } + + return ret; +} + +static bool unic_is_channels_param_changed(struct unic_dev *unic_dev, + struct ethtool_ringparam *param, + struct kernel_ethtool_ringparam *kernel_param, + struct unic_channel_param *channels_param) +{ + channels_param->sqebb_depth = roundup_pow_of_two(param->tx_pending); + channels_param->rqe_depth = roundup_pow_of_two(param->rx_pending); + channels_param->rx_buff_len = kernel_param->rx_buf_len; + + if (unic_dev->channels.sqebb_depth != channels_param->sqebb_depth) + set_bit(UNIC_TX_CHANGED, &unic_dev->channels.state); + + if (unic_dev->channels.rx_buff_len != channels_param->rx_buff_len || + unic_dev->channels.rqe_depth != channels_param->rqe_depth) + set_bit(UNIC_RX_CHANGED, &unic_dev->channels.state); + + return unic_tx_changed(unic_dev) || unic_rx_changed(unic_dev); +} + +static int unic_check_channels_param(struct unic_dev *unic_dev, + struct ethtool_ringparam *param, + struct kernel_ethtool_ringparam *kernel_param) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_adev_caps *unic_caps = ubase_get_unic_caps(adev); + struct net_device *netdev = unic_dev->comdev.netdev; + u32 i; + + if (netif_running(netdev)) { + unic_err(unic_dev, + "failed to set channels param, due to netdev is up, down it first and try again.\n"); + return -EBUSY; + } + + if (__unic_resetting(unic_dev) || !unic_dev->channels.c) { + unic_err(unic_dev, + "failed to check param value, due to dev resetting or uninited\n"); + return -EBUSY; + } + + for (i = 0; i < unic_dev->channels.num; i++) { + if (!unic_dev->channels.c[i].sq || + !unic_dev->channels.c[i].rq) { + unic_err(unic_dev, "channel(%u) sq or rq is null.\n", i); + return -ENXIO; + } + } + + if (param->rx_mini_pending || param->rx_jumbo_pending) + return -EINVAL; + + if (param->tx_pending > unic_caps->jfs.depth || + param->tx_pending < UNIC_TXRX_MIN_DEPTH || + param->rx_pending > unic_caps->jfr.depth || + param->rx_pending < UNIC_TXRX_MIN_DEPTH) { + unic_err(unic_dev, "queue depth out of range [%d, %u]\n", + UNIC_TXRX_MIN_DEPTH, unic_caps->jfs.depth); + return -EINVAL; + } + + if (kernel_param->rx_buf_len != UNIC_RX_BUF_LEN_2K && + kernel_param->rx_buf_len != UNIC_RX_BUF_LEN_4K) { + unic_err(unic_dev, "rx buf len only support %d and %d.\n", + UNIC_RX_BUF_LEN_2K, UNIC_RX_BUF_LEN_4K); + return -EINVAL; + } + + return 0; +} + +static void unic_dev_change_channels_param(struct unic_dev *unic_dev, + struct unic_channel_param param) +{ + struct unic_channels *channels = &unic_dev->channels; + + channels->sqebb_depth = param.sqebb_depth; + channels->rqe_depth = param.rqe_depth; + channels->sq_cqe_depth = param.sqebb_depth; + channels->rq_cqe_depth = param.rqe_depth; + + channels->rx_buff_len = param.rx_buff_len; + channels->sqebb_shift = ilog2(roundup_pow_of_two(channels->sqebb_depth)); + channels->sq_jfc_shift = ilog2(roundup_pow_of_two(channels->sq_cqe_depth)); + channels->rq_jfc_shift = ilog2(roundup_pow_of_two(channels->rq_cqe_depth)); +} + +static void unic_clear_txrx_channels_state(struct unic_dev *unic_dev) +{ + clear_bit(UNIC_TX_CHANGED, &unic_dev->channels.state); + clear_bit(UNIC_RX_CHANGED, &unic_dev->channels.state); +} + +static void unic_uninit_changed_channels(struct unic_dev *unic_dev) +{ + struct unic_channels *channels = &unic_dev->channels; + + if (unic_tx_changed(unic_dev)) + unic_destroy_tx(unic_dev, channels->num); + + if (unic_rx_changed(unic_dev)) + unic_destroy_rx(unic_dev, channels->num); +} + +static int unic_init_changed_channels(struct unic_dev *unic_dev) +{ + struct unic_channels *channels = &unic_dev->channels; + int ret = 0; + + if (unic_tx_changed(unic_dev)) { + ret = unic_init_tx(unic_dev, channels->num); + if (ret) + goto out; + } + + if (unic_rx_changed(unic_dev)) { + ret = unic_init_rx(unic_dev, channels->num); + if (ret) + goto err_init_rx_channels; + } + + goto out; + +err_init_rx_channels: + if (unic_tx_changed(unic_dev)) + unic_destroy_tx(unic_dev, channels->num); +out: + return ret; +} + +static int unic_modify_channels(struct unic_dev *unic_dev, + struct unic_channel_param new_param) +{ + struct unic_channels *channels = &unic_dev->channels; + int ret; + + mutex_lock(&channels->mutex); + unic_uninit_changed_channels(unic_dev); + + unic_dev_change_channels_param(unic_dev, new_param); + + ret = unic_init_changed_channels(unic_dev); + + mutex_unlock(&channels->mutex); + return ret; +} + +static void unic_backup_channels_param(struct unic_dev *unic_dev, + struct unic_channel_param *channels_param) +{ + channels_param->sqebb_depth = unic_dev->channels.sqebb_depth; + channels_param->rqe_depth = unic_dev->channels.rqe_depth; + channels_param->rx_buff_len = unic_dev->channels.rx_buff_len; +} + +static int unic_alloc_txrx_stats(struct unic_dev *unic_dev, + struct unic_sq_stats **sq_stats, + struct unic_rq_stats **rq_stats) +{ + if (unic_tx_changed(unic_dev)) { + *sq_stats = kcalloc(unic_dev->channels.num, sizeof(**sq_stats), + GFP_KERNEL); + if (ZERO_OR_NULL_PTR(*sq_stats)) + return -ENOMEM; + } + + if (unic_rx_changed(unic_dev)) { + *rq_stats = kcalloc(unic_dev->channels.num, sizeof(**rq_stats), + GFP_KERNEL); + if (ZERO_OR_NULL_PTR(*rq_stats)) { + if (unic_tx_changed(unic_dev)) + kfree(*sq_stats); + return -ENOMEM; + } + } + + return 0; +} + +static void unic_free_txrx_stats(struct unic_dev *unic_dev, + struct unic_sq_stats *sq_stats, + struct unic_rq_stats *rq_stats) +{ + if (unic_tx_changed(unic_dev)) + kfree(sq_stats); + + if (unic_rx_changed(unic_dev)) + kfree(rq_stats); +} + +static void unic_backup_sqrq_stats(struct unic_dev *unic_dev, + struct unic_sq_stats *sq_stats, + struct unic_rq_stats *rq_stats) +{ + u32 i; + + for (i = 0; i < unic_dev->channels.num; i++) { + if (unic_tx_changed(unic_dev)) + memcpy(&sq_stats[i], &unic_dev->channels.c[i].sq->stats, + sizeof(struct unic_sq_stats)); + if (unic_rx_changed(unic_dev)) + memcpy(&rq_stats[i], &unic_dev->channels.c[i].rq->stats, + sizeof(struct unic_rq_stats)); + } +} + +static void unic_backup_param_and_stats(struct unic_dev *unic_dev, + struct unic_channel_param *channels_param, + struct unic_sq_stats *sq_stats, + struct unic_rq_stats *rq_stats) +{ + unic_backup_channels_param(unic_dev, channels_param); + unic_backup_sqrq_stats(unic_dev, sq_stats, rq_stats); +} + +static void unic_restore_sqrq_stats(struct unic_dev *unic_dev, + struct unic_sq_stats *sq_stats, + struct unic_rq_stats *rq_stats) +{ + u32 i; + + for (i = 0; i < unic_dev->channels.num; i++) { + if (unic_rx_changed(unic_dev)) + memcpy(&unic_dev->channels.c[i].rq->stats, &rq_stats[i], + sizeof(struct unic_rq_stats)); + if (unic_tx_changed(unic_dev)) + memcpy(&unic_dev->channels.c[i].sq->stats, &sq_stats[i], + sizeof(struct unic_sq_stats)); + } +} + +int unic_set_channels_param(struct net_device *ndev, + struct ethtool_ringparam *param, + struct kernel_ethtool_ringparam *kernel_param, + struct netlink_ext_ack *extack) +{ + struct unic_channel_param new_param, org_param; + struct unic_dev *unic_dev = netdev_priv(ndev); + struct unic_sq_stats *sq_stats; + struct unic_rq_stats *rq_stats; + int ret, ret1; + + ret = unic_check_channels_param(unic_dev, param, kernel_param); + if (ret) + return ret; + + if (!unic_is_channels_param_changed(unic_dev, param, kernel_param, &new_param)) + return 0; + + ret = unic_alloc_txrx_stats(unic_dev, &sq_stats, &rq_stats); + if (ret) { + unic_err(unic_dev, "failed to alloc txrx stats, ret = %d.\n", ret); + goto out; + } + + unic_backup_param_and_stats(unic_dev, &org_param, sq_stats, rq_stats); + + unic_info(unic_dev, + "changing tx/rx depth from %u/%u to %u/%u, changing rx buffer len from %u to %u.\n", + unic_dev->channels.sqebb_depth, unic_dev->channels.rqe_depth, + new_param.sqebb_depth, new_param.rqe_depth, + unic_dev->channels.rx_buff_len, new_param.rx_buff_len); + + ret = unic_modify_channels(unic_dev, new_param); + if (ret) { + unic_err(unic_dev, + "failed to modify channels, revert to old value, ret = %d.\n", + ret); + ret1 = unic_modify_channels(unic_dev, org_param); + if (ret1) { + unic_err(unic_dev, + "failed to revert to old value, ret1 = %d.\n", + ret1); + unic_free_txrx_stats(unic_dev, sq_stats, rq_stats); + unic_clear_txrx_channels_state(unic_dev); + return ret1; + } + } + + unic_restore_sqrq_stats(unic_dev, sq_stats, rq_stats); + unic_free_txrx_stats(unic_dev, sq_stats, rq_stats); + +out: + unic_clear_txrx_channels_state(unic_dev); + + return ret; +} + +void unic_get_channels_param(struct net_device *ndev, + struct ethtool_ringparam *param, + struct kernel_ethtool_ringparam *kernel_param, + struct netlink_ext_ack *extack) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_adev_caps *unic_caps = ubase_get_unic_caps(adev); + + param->tx_max_pending = unic_caps->jfs.depth; + param->rx_max_pending = unic_caps->jfr.depth; + + param->tx_pending = unic_dev->channels.sqebb_depth; + param->rx_pending = unic_dev->channels.rqe_depth; + kernel_param->rx_buf_len = unic_dev->channels.rx_buff_len; +} diff --git a/drivers/net/ub/unic/unic_channel.h b/drivers/net/ub/unic/unic_channel.h new file mode 100644 index 0000000000000000000000000000000000000000..54889c6f725da14f5c6e11518b87f43858ed4b56 --- /dev/null +++ b/drivers/net/ub/unic/unic_channel.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_CHANNEL_H__ +#define __UNIC_CHANNEL_H__ + +#include +#include + +struct unic_channel_param { + u32 sqebb_depth; + u32 rqe_depth; + u32 rx_buff_len; +}; + +int unic_set_channels(struct net_device *ndev, struct ethtool_channels *ch); +void unic_get_channels(struct net_device *ndev, struct ethtool_channels *ch); +int unic_set_channels_param(struct net_device *ndev, + struct ethtool_ringparam *param, + struct kernel_ethtool_ringparam *kernel_param, + struct netlink_ext_ack *extack); +void unic_get_channels_param(struct net_device *ndev, + struct ethtool_ringparam *param, + struct kernel_ethtool_ringparam *kernel_param, + struct netlink_ext_ack *extack); + +#endif diff --git a/drivers/net/ub/unic/unic_cmd.h b/drivers/net/ub/unic/unic_cmd.h new file mode 100644 index 0000000000000000000000000000000000000000..bf3e11e343cd140a4192d8908438e23431b40914 --- /dev/null +++ b/drivers/net/ub/unic/unic_cmd.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_CMD_H__ +#define __UNIC_CMD_H__ + +#include +#include +#include + +#include "unic.h" + +struct unic_res_cmd_resp { + __le32 cap_bits[UNIC_CAP_LEN]; + __le32 rsvd[3]; + + u8 media_type; + u8 default_lanes; + __le16 rx_buff_len; + __le32 speed_ability; + __le32 default_speed; + __le16 total_ip_tbl_size; + u8 rsvd1[2]; + __le32 guid_tbl_size; + u8 rsvd2[4]; + __le32 uc_mac_tbl_size; + __le32 vlan_tbl_size; + + __le32 mng_tbl_size; + __le16 max_trans_unit; + __le16 min_trans_unit; + u8 rsvd3[4]; + __le16 vport_buf_size; /* unit: KB */ + u8 vport_buf_num; + u8 rsvd4[5]; + __le16 max_int_ql; + __le16 max_int_gl; + __le32 mc_mac_tbl_size; + u8 rsvd5[4]; +}; + +struct unic_config_max_trans_unit_cmd { + __le16 max_trans_unit; + u8 rsv[22]; +}; + +struct unic_ld_config_mode_cmd { + __le32 txrx_en; + u8 rsv[20]; +}; + +struct unic_link_status_cmd_resp { + u8 status; + u8 link_fail_code; + u8 rsvd[22]; +}; + +struct unic_query_port_info_resp { + __le32 speed; + __le32 speed_ability; + __le16 fec_mode; + __le16 fec_ability; + u8 autoneg; + u8 autoneg_ability; + u8 lanes; + u8 module_type; + u8 rsv[8]; +}; + +struct unic_cfg_speed_dup_cmd { + __le32 speed; + u8 duplex; + u8 lanes; + u8 rsv[18]; +}; + +struct unic_cfg_autoneg_mode_cmd { + u8 autoneg_en; + u8 rsv[23]; +}; + +struct unic_promisc_cfg_cmd { + u8 rsv0; + u8 promisc_uc_ind : 1; /* indicate if uc enable is set or not */ + u8 promisc_mc_ind : 1; /* indicate if mc enable is set or not */ + u8 rsv1 : 6; + u8 promisc_rx_uc_ip_en : 1; /* promisc rx ip uc enable or not */ + u8 promisc_rx_uc_guid_en : 1; /* promisc rx guid uc enable or not */ + u8 promisc_rx_mc_en : 1; /* promisc rx mc enable or not */ + u8 promisc_rx_uc_mac_en : 1; /* promisc rx mac uc enable or not */ + u8 promisc_rx_mc_mac_en : 1; /* promisc rx mac mc enable or not */ + u8 promisc_rx_bc_en : 1; /* promisc rx bc enable or not */ + u8 rsv2 : 2; + u8 rsv3[21]; +}; + +struct unic_query_net_guid_cmd { + u8 guid[16]; + u8 rsvd[8]; +}; + +struct unic_cfg_vport_buf_cmd { + u8 buf_num; + u8 rsvd[7]; +#define U32S_PER_U64 2 + __le32 buf_addr[UNIC_MAX_VPORT_BUF_NUM * U32S_PER_U64]; +}; + +struct unic_cfg_fec_cmd { + __le32 fec_mode; + u8 rsv[20]; +}; + +struct unic_query_fec_stats_item { + __le32 corr_blocks_l; + __le32 corr_blocks_h; + __le32 uncorr_blocks_l; + __le32 uncorr_blocks_h; + __le32 corr_bits_l; + __le32 corr_bits_h; +}; + +#define UNIC_FEC_STATS_MAX_LANE 8 +struct unic_query_fec_stats_resp { + struct unic_query_fec_stats_item total; + struct unic_query_fec_stats_item lane[UNIC_FEC_STATS_MAX_LANE]; + u8 lane_num; + u8 rsv[31]; +}; + +struct unic_query_flush_status_resp { + u8 status; + u8 rsv[23]; +}; + +enum unic_vl_map_type { + UNIC_PRIO_VL_MAP, + UNIC_DSCP_VL_MAP, +}; + +struct unic_config_vl_map_cmd { + u8 map_type; + u8 resv0[3]; + u8 prio_vl[UNIC_MAX_PRIO_NUM]; + u8 dscp_vl[UBASE_MAX_DSCP]; + u8 resv1[12]; +}; + +struct unic_config_vl_speed_cmd { + __le16 bus_ue_id; + __le16 vl_bitmap; + __le32 max_speed[UBASE_MAX_VL_NUM]; + u8 resv1[20]; +}; + +#endif diff --git a/drivers/net/ub/unic/unic_comm_addr.c b/drivers/net/ub/unic/unic_comm_addr.c new file mode 100644 index 0000000000000000000000000000000000000000..cc2822453614e20630caf8a5854bcd81b4ea09fa --- /dev/null +++ b/drivers/net/ub/unic/unic_comm_addr.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include + +#include "unic_comm_addr.h" + +struct unic_comm_addr_node * +unic_comm_find_addr_node(struct list_head *list, const u8 *addr, u16 node_mask) +{ + struct unic_comm_addr_node *addr_node, *tmp; + + list_for_each_entry_safe(addr_node, tmp, list, node) { + if (unic_comm_addr_equal(addr_node->unic_addr, addr, + addr_node->node_mask, node_mask)) + return addr_node; + } + + return NULL; +} + +void unic_comm_update_addr_state(struct unic_comm_addr_node *addr_node, + enum UNIC_COMM_ADDR_STATE state) +{ + switch (state) { + case UNIC_COMM_ADDR_TO_ADD: + if (addr_node->state == UNIC_COMM_ADDR_TO_DEL) + addr_node->state = UNIC_COMM_ADDR_ACTIVE; + break; + case UNIC_COMM_ADDR_TO_DEL: + if (addr_node->state == UNIC_COMM_ADDR_TO_ADD) { + list_del(&addr_node->node); + kfree(addr_node); + } else { + addr_node->state = UNIC_COMM_ADDR_TO_DEL; + } + break; + case UNIC_COMM_ADDR_ACTIVE: + if (addr_node->state == UNIC_COMM_ADDR_TO_ADD) + addr_node->state = UNIC_COMM_ADDR_ACTIVE; + break; + default: + break; + } +} + +int unic_comm_update_addr_list(struct list_head *list, + spinlock_t *addr_list_lock, + enum UNIC_COMM_ADDR_STATE state, + const u8 *addr) +{ + struct unic_comm_addr_node *addr_node; + + spin_lock_bh(addr_list_lock); + + /* if the addr is already in the addr list, no need to add a new + * one into it, just check the addr state, convert it to a new + * state, or just remove it, or do nothing. + */ + addr_node = unic_comm_find_addr_node(list, addr, UNIC_COMM_ADDR_NO_MASK); + if (addr_node) { + unic_comm_update_addr_state(addr_node, state); + spin_unlock_bh(addr_list_lock); + return 0; + } + + /* if this addr is never added, unnecessary to delete */ + if (state == UNIC_COMM_ADDR_TO_DEL) { + spin_unlock_bh(addr_list_lock); + return -ENOENT; + } + + addr_node = kzalloc(sizeof(*addr_node), GFP_ATOMIC); + if (!addr_node) { + spin_unlock_bh(addr_list_lock); + return -ENOMEM; + } + + addr_node->state = state; + memcpy(addr_node->unic_addr, addr, UNIC_ADDR_LEN); + list_add_tail(&addr_node->node, list); + + spin_unlock_bh(addr_list_lock); + + return 0; +} + +void unic_comm_sync_from_addr_del_list(struct list_head *del_list, + struct list_head *addr_list) +{ + struct unic_comm_addr_node *addr_node, *tmp, *new_node; + + list_for_each_entry_safe(addr_node, tmp, del_list, node) { + new_node = unic_comm_find_addr_node(addr_list, + addr_node->unic_addr, + UNIC_COMM_ADDR_NO_MASK); + if (new_node) { + /* If the addr exists in the addr list, it means + * received a new TO_ADD request during the time window + * of configuring the addr, so we just need + * to change the addr node state to ACTIVE. + */ + new_node->state = UNIC_COMM_ADDR_ACTIVE; + list_del(&addr_node->node); + kfree(addr_node); + } else { + list_move_tail(&addr_node->node, addr_list); + } + } +} + +void unic_comm_sync_from_addr_add_list(struct list_head *add_list, + struct list_head *addr_list, + bool *all_added) +{ + struct unic_comm_addr_node *addr_node, *tmp, *new_node; + + list_for_each_entry_safe(addr_node, tmp, add_list, node) { + if (*all_added && addr_node->state == UNIC_COMM_ADDR_TO_ADD) + *all_added = false; + + /* If the addr from tmp_add_list is not in the + * addr_list, it means have received a TO_DEL request + * during the time window of adding the addr. If addr_node + * state is ACTIVE, then change its state to TO_DEL, + * then it will be removed at next time. If is TO_ADD, + * it means this address hasn't been added successfully, + * so just remove the addr node. + */ + new_node = unic_comm_find_addr_node(addr_list, + addr_node->unic_addr, + UNIC_COMM_ADDR_NO_MASK); + if (new_node) { + unic_comm_update_addr_state(new_node, addr_node->state); + list_del(&addr_node->node); + kfree(addr_node); + } else if (addr_node->state == UNIC_COMM_ADDR_ACTIVE) { + addr_node->state = UNIC_COMM_ADDR_TO_DEL; + list_move_tail(&addr_node->node, addr_list); + } else { + list_del(&addr_node->node); + kfree(addr_node); + } + } +} + +bool unic_comm_sync_addr_table(struct unic_vport *vport, + struct list_head *list, + spinlock_t *addr_list_lock, + void (*sync)(struct unic_vport *vport, + struct list_head *list), + void (*unsync)(struct unic_vport *vport, + struct list_head *list)) +{ + struct unic_comm_addr_node *addr_node, *tmp, *new_node; + struct list_head tmp_add_list, tmp_del_list; + bool all_added = true; + + INIT_LIST_HEAD(&tmp_add_list); + INIT_LIST_HEAD(&tmp_del_list); + + /* move the addr to the tmp_add_list and tmp_del_list, then + * we can add/delete these addr outside the spin lock + */ + spin_lock_bh(addr_list_lock); + + list_for_each_entry_safe(addr_node, tmp, list, node) { + switch (addr_node->state) { + case UNIC_COMM_ADDR_TO_DEL: + list_move_tail(&addr_node->node, &tmp_del_list); + break; + case UNIC_COMM_ADDR_TO_ADD: + new_node = kzalloc(sizeof(*new_node), GFP_ATOMIC); + if (!new_node) + goto stop_traverse; + memcpy(new_node->unic_addr, addr_node->unic_addr, + UNIC_ADDR_LEN); + new_node->state = addr_node->state; + new_node->node_mask = addr_node->node_mask; + list_add_tail(&new_node->node, &tmp_add_list); + break; + default: + break; + } + } + +stop_traverse: + spin_unlock_bh(addr_list_lock); + + /* delete first, in order to get max addr table space for adding */ + if (unsync) + unsync(vport, &tmp_del_list); + if (sync) + sync(vport, &tmp_add_list); + + /* if some addr were added/deleted fail, move back to the + * addr_list, and retry at next time. + */ + spin_lock_bh(addr_list_lock); + + unic_comm_sync_from_addr_del_list(&tmp_del_list, list); + unic_comm_sync_from_addr_add_list(&tmp_add_list, list, &all_added); + + spin_unlock_bh(addr_list_lock); + + return all_added; +} + +int unic_convert_ip_addr(struct sockaddr *addr, struct in6_addr *ip_addr) +{ + __be32 v4addr; + + switch (addr->sa_family) { + case AF_INET: + /* we transform ipv4 addr to ipv6 addr for later configuring */ + v4addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + ipv6_addr_set_v4mapped(v4addr, ip_addr); + break; + case AF_INET6: + memcpy(ip_addr, &((struct sockaddr_in6 *)addr)->sin6_addr, + sizeof(struct in6_addr)); + break; + default: + return -EINVAL; + } + return 0; +} diff --git a/drivers/net/ub/unic/unic_comm_addr.h b/drivers/net/ub/unic/unic_comm_addr.h new file mode 100644 index 0000000000000000000000000000000000000000..ae390e4a7f079077d4e5d6ca8b85c86a501e5468 --- /dev/null +++ b/drivers/net/ub/unic/unic_comm_addr.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_COMM_ADDR__ +#define __UNIC_COMM_ADDR__ + +#include +#include + +#include "unic_dev.h" + +enum UNIC_COMM_ADDR_STATE { + UNIC_COMM_ADDR_TO_ADD, + UNIC_COMM_ADDR_TO_DEL, + UNIC_COMM_ADDR_ACTIVE +}; + +#define UNIC_COMM_ADDR_NO_MASK 0 +#define UNIC_IPV4_PREFIX 0xffff0000 + +#define UNIC_ADDR_LEN 16 +struct unic_comm_addr_node { + struct list_head node; + enum UNIC_COMM_ADDR_STATE state; + union { + u8 unic_addr[UNIC_ADDR_LEN]; + struct in6_addr ip_addr; + }; + u16 node_mask; +}; + +static inline bool unic_comm_addr_equal(const u8 *addr1, const u8 *addr2, + u16 mask1, u16 mask2) +{ + const u32 *a = (const u32 *)addr1; + const u32 *b = (const u32 *)addr2; + + return (((a[0] ^ b[0]) | (a[1] ^ b[1]) | + (a[2] ^ b[2]) | (a[3] ^ b[3])) | + (mask1 ^ mask2)) == 0; +} + +struct unic_comm_addr_node *unic_comm_find_addr_node(struct list_head *list, + const u8 *addr, + u16 node_mask); +void unic_comm_update_addr_state(struct unic_comm_addr_node *addr_node, + enum UNIC_COMM_ADDR_STATE state); +int unic_comm_update_addr_list(struct list_head *list, + spinlock_t *addr_list_lock, + enum UNIC_COMM_ADDR_STATE state, + const u8 *addr); +bool unic_comm_sync_addr_table(struct unic_vport *vport, + struct list_head *list, + spinlock_t *addr_list_lock, + void (*sync)(struct unic_vport *, + struct list_head *), + void (*unsync)(struct unic_vport *, + struct list_head *)); +void unic_comm_sync_from_addr_del_list(struct list_head *del_list, + struct list_head *addr_list); +void unic_comm_sync_from_addr_add_list(struct list_head *add_list, + struct list_head *addr_list, + bool *all_added); +int unic_convert_ip_addr(struct sockaddr *addr, struct in6_addr *ip_addr); + +#endif diff --git a/drivers/net/ub/unic/unic_crq.c b/drivers/net/ub/unic/unic_crq.c new file mode 100644 index 0000000000000000000000000000000000000000..29a27c6c547d21fbcc2f6ec59a6ad9ddb1c386fb --- /dev/null +++ b/drivers/net/ub/unic/unic_crq.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "unic_cmd.h" +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_netdev.h" +#include "unic_crq.h" + +static void __unic_handle_link_status_event(struct auxiliary_device *adev, + u8 hw_link_status) +{ + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + u8 link_status; + + if (test_and_set_bit(UNIC_STATE_LINK_UPDATING, &unic_dev->state)) + return; + + unic_dbg(unic_dev, "HW link_event status = %u.\n", hw_link_status); + + unic_dev->hw.mac.link_status = hw_link_status; + if (unic_dev_ubl_supported(unic_dev)) + hw_link_status = UNIC_LINK_STATUS_UP; + + link_status = test_bit(UNIC_STATE_DOWN, + &unic_dev->state) ? 0 : hw_link_status; + if (link_status != unic_dev->sw_link_status) { + unic_dev->sw_link_status = link_status; + unic_link_status_change(unic_dev->comdev.netdev, link_status); + } + + clear_bit(UNIC_STATE_LINK_UPDATING, &unic_dev->state); +} + +int unic_handle_link_status_event(void *dev, void *data, u32 len) +{ + struct unic_link_status_cmd_resp *resp = data; + struct auxiliary_device *adev = dev; + u8 hw_link_status = resp->status; + + __unic_handle_link_status_event(adev, hw_link_status); + + return 0; +} diff --git a/drivers/net/ub/unic/unic_crq.h b/drivers/net/ub/unic/unic_crq.h new file mode 100644 index 0000000000000000000000000000000000000000..0b7e9afa50dfa3df507412879dca35f015cdf727 --- /dev/null +++ b/drivers/net/ub/unic/unic_crq.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_CRQ_H__ +#define __UNIC_CRQ_H__ + +int unic_handle_link_status_event(void *dev, void *data, u32 len); + +#endif diff --git a/drivers/net/ub/unic/unic_dcbnl.c b/drivers/net/ub/unic/unic_dcbnl.c new file mode 100644 index 0000000000000000000000000000000000000000..3110277da020345f7fb62bfbccef8010a27dea82 --- /dev/null +++ b/drivers/net/ub/unic/unic_dcbnl.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "unic_hw.h" +#include "unic_netdev.h" +#include "unic_dcbnl.h" + +static const struct dcbnl_rtnl_ops unic_dcbnl_ops = { +}; + +void unic_set_dcbnl_ops(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + if (!unic_dev_ets_supported(unic_dev)) + return; + + netdev->dcbnl_ops = &unic_dcbnl_ops; + + unic_dev->dcbx_cap = DCB_CAP_DCBX_VER_IEEE | DCB_CAP_DCBX_HOST; +} diff --git a/drivers/net/ub/unic/unic_dcbnl.h b/drivers/net/ub/unic/unic_dcbnl.h new file mode 100644 index 0000000000000000000000000000000000000000..a721fa51d6e4c97973b5b523037363c7010e54a7 --- /dev/null +++ b/drivers/net/ub/unic/unic_dcbnl.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_DCBNL_H__ +#define __UNIC_DCBNL_H__ + +#include + +#ifdef CONFIG_UB_UNIC_DCB +void unic_set_dcbnl_ops(struct net_device *netdev); +#else +static inline void unic_set_dcbnl_ops(struct net_device *netdev) {} +#endif + +#endif diff --git a/drivers/net/ub/unic/unic_dev.c b/drivers/net/ub/unic/unic_dev.c new file mode 100644 index 0000000000000000000000000000000000000000..a0ea8edee2efbdec6db6104ffe402cd472da8b3a --- /dev/null +++ b/drivers/net/ub/unic/unic_dev.c @@ -0,0 +1,1000 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include +#include +#ifdef CONFIG_UB_UNIC_UBL +#include +#endif +#include +#include +#include +#include + +#include "unic_cmd.h" +#include "unic_dcbnl.h" +#include "unic_ethtool.h" +#include "unic_event.h" +#include "unic_guid.h" +#include "unic_hw.h" +#include "unic_qos_hw.h" +#include "unic_netdev.h" +#include "unic_rack_ip.h" +#include "unic_dev.h" + +#define UNIC_WATCHDOG_TIMEOUT (5 * HZ) + +#ifndef UB_DATA_LEN +#define UB_DATA_LEN 1500 +#endif + +static int netif_debug = -1; +module_param(netif_debug, int, 0); +MODULE_PARM_DESC(netif_debug, "network interface message level setting"); + +static int unic_debug; +module_param_named(debug, unic_debug, int, 0644); +MODULE_PARM_DESC(debug, "enable unic debug log, 0:disable, others:enable, default:0"); + +#define DEFAULT_MSG_LEVEL (NETIF_MSG_PROBE | NETIF_MSG_LINK | \ + NETIF_MSG_IFDOWN | NETIF_MSG_IFUP) + +static struct workqueue_struct *unic_wq; + +int unic_dbg_log(void) +{ + return unic_debug; +} + +u32 unic_channels_max_num(struct auxiliary_device *adev) +{ + struct ubase_adev_caps *unic_caps = ubase_get_unic_caps(adev); + u32 jfs_num = unic_caps->jfs.max_cnt; + u32 jfr_num = unic_caps->jfr.max_cnt; + u32 jfc_num = unic_caps->jfc.max_cnt; + + return min(min(jfs_num, jfr_num), jfc_num >> 1); +} + +u32 unic_get_max_rss_size(struct unic_dev *unic_dev) +{ + u8 vl_num = unic_dev->channels.rss_vl_num; + + return unic_channels_max_num(unic_dev->comdev.adev) / vl_num; +} + +void unic_update_queue_info(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_adev_qos *qos = ubase_get_adev_qos(adev); + struct unic_channels *channels = &unic_dev->channels; + struct unic_vl *vl = &channels->vl; + u32 i; + + for (i = 0; i < qos->nic_vl_num; i++) { + if (i < channels->rss_vl_num) { + vl->queue_count[i] = channels->rss_size; + vl->queue_offset[i] = i * channels->rss_size; + } else { + vl->queue_count[i] = 0; + vl->queue_offset[i] = channels->rss_vl_num * + channels->rss_size; + } + } +} + +static int unic_update_vl_sl_map(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_adev_qos *qos = ubase_get_adev_qos(adev); + u8 i; + + if (!qos) { + dev_err(adev->dev.parent, "failed to get qos info\n"); + return -ENODATA; + } + + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (qos->ue_sl_vl[i] >= UBASE_MAX_VL_NUM) { + dev_err(adev->dev.parent, + "the sl(%u) map to vl(%u) exceed max_vl_num(%u).\n", + i, qos->ue_sl_vl[i], UBASE_MAX_VL_NUM); + return -EINVAL; + } + + unic_dev->channels.vl.vl_sl[qos->ue_sl_vl[i]] = i; + } + + return 0; +} + +static inline void unic_get_hw_prio_vl(struct ubase_caps *caps, u8 *sw_prio_vl, + u8 *hw_prio_vl) +{ + int i; + + for (i = 0; i < UNIC_MAX_PRIO_NUM; i++) + hw_prio_vl[i] = caps->req_vl[sw_prio_vl[i]]; +} + +static inline void unic_get_hw_dscp_vl(struct ubase_caps *caps, u8 *hw_prio_vl, + u8 *dscp_prio, u8 *hw_dscp_vl) +{ + int i; + + for (i = 0; i < UBASE_MAX_DSCP; i++) + hw_dscp_vl[i] = dscp_prio[i] == UNIC_INVALID_PRIORITY ? + caps->req_vl[0] : hw_prio_vl[dscp_prio[i]]; +} + +int unic_set_vl_map(struct unic_dev *unic_dev, u8 *dscp_prio, u8 *prio_vl, + u8 map_type) +{ + struct ubase_caps *caps = ubase_get_dev_caps(unic_dev->comdev.adev); + u8 hw_prio_vl[UNIC_MAX_PRIO_NUM]; + u8 hw_dscp_vl[UBASE_MAX_DSCP]; + int ret; + + unic_get_hw_prio_vl(caps, prio_vl, hw_prio_vl); + unic_get_hw_dscp_vl(caps, hw_prio_vl, dscp_prio, hw_dscp_vl); + + if (unic_dev_ets_supported(unic_dev) && + !unic_dev_ubl_supported(unic_dev)) { + ret = unic_set_hw_vl_map(unic_dev, hw_dscp_vl, hw_prio_vl, + map_type); + if (ret) + return ret; + } + + ubase_update_udma_dscp_vl(unic_dev->comdev.adev, hw_dscp_vl, + UBASE_MAX_DSCP); + + return 0; +} + +static int unic_init_vl_map(struct unic_dev *unic_dev) +{ + struct unic_vl *vl = &unic_dev->channels.vl; + int i; + + for (i = 0; i < UBASE_MAX_DSCP; i++) + vl->dscp_prio[i] = UNIC_INVALID_PRIORITY; + + return unic_set_vl_map(unic_dev, vl->dscp_prio, + vl->prio_vl, UNIC_PRIO_VL_MAP); +} + +static void unic_vl_bitmap_init(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_caps *caps = ubase_get_dev_caps(adev); + u8 i; + + for (i = 0; i < caps->vl_num; i++) { + unic_dev->channels.vl.vl_bitmap |= 1 << caps->req_vl[i]; + unic_dev->channels.vl.vl_bitmap |= 1 << caps->resp_vl[i]; + } +} + +static int unic_init_vl_sch(struct unic_dev *unic_dev) +{ +#define UNIC_BW_PERCENT 100 + + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_caps *caps = ubase_get_dev_caps(adev); + struct unic_vl *vl = &unic_dev->channels.vl; + u8 vl_tsa[UBASE_MAX_VL_NUM] = {0}; + u8 vl_bw[UBASE_MAX_VL_NUM] = {0}; + int quo, rem; + u32 i; + + if (!unic_dev_ets_supported(unic_dev)) + return 0; + + quo = UNIC_BW_PERCENT / caps->vl_num; + rem = UNIC_BW_PERCENT % caps->vl_num; + + for (i = 0; i < caps->vl_num; i++) { + vl->vl_tsa[i] = UNIC_VL_TSA_DWRR; + vl->vl_bw[i] = quo; + if (i < rem) + vl->vl_bw[i]++; + + vl_bw[caps->req_vl[i]] = vl->vl_bw[i]; + vl_bw[caps->resp_vl[i]] = vl->vl_bw[i]; + vl_tsa[caps->req_vl[i]] = vl->vl_tsa[i]; + vl_tsa[caps->resp_vl[i]] = vl->vl_tsa[i]; + } + + return ubase_config_tm_vl_sch(adev, vl->vl_bitmap, vl_bw, vl_tsa); +} + +static int unic_init_vl_maxrate(struct unic_dev *unic_dev) +{ + u64 max_speed = unic_dev->hw.mac.max_speed; + u64 vl_maxrate[UBASE_MAX_VL_NUM]; + u8 i; + + if (!unic_dev_ets_supported(unic_dev) || + !unic_dev_tc_speed_limit_supported(unic_dev)) + return 0; + + for (i = 0; i < UBASE_MAX_VL_NUM; i++) + vl_maxrate[i] = max_speed * UNIC_MBYTE_PER_SEND; + + return unic_config_vl_rate_limit(unic_dev, vl_maxrate, + unic_dev->channels.vl.vl_bitmap); +} + +static int unic_init_vl_info(struct unic_dev *unic_dev) +{ + int ret; + + unic_vl_bitmap_init(unic_dev); + unic_update_queue_info(unic_dev); + + ret = unic_update_vl_sl_map(unic_dev); + if (ret) + return ret; + + ret = unic_init_vl_map(unic_dev); + if (ret) + return ret; + + ret = unic_init_vl_maxrate(unic_dev); + if (ret) + return ret; + + return unic_init_vl_sch(unic_dev); +} + +static int unic_init_channels_attr(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channels *channels = &unic_dev->channels; + struct ubase_adev_caps *unic_caps; + int ret; + + mutex_init(&channels->mutex); + + unic_caps = ubase_get_unic_caps(adev); + + channels->vl.vl_num = 1; + channels->rss_vl_num = 1; + channels->rss_size = unic_channels_max_num(unic_dev->comdev.adev); + channels->num = channels->rss_size * channels->rss_vl_num; + channels->sqebb_depth = unic_caps->jfs.depth; + channels->rqe_depth = unic_caps->jfr.depth; + channels->sq_cqe_depth = unic_caps->jfc.depth; + channels->rq_cqe_depth = unic_caps->jfc.depth; + channels->cqe_size = unic_caps->cqe_size; + channels->rx_buff_len = unic_dev->caps.rx_buff_len; + channels->sqebb_shift = ilog2(roundup_pow_of_two(channels->sqebb_depth)); + channels->sq_jfc_shift = ilog2(roundup_pow_of_two(channels->sq_cqe_depth)); + channels->rq_jfc_shift = ilog2(roundup_pow_of_two(channels->rq_cqe_depth)); + + ret = unic_init_vl_info(unic_dev); + if (ret) + mutex_destroy(&channels->mutex); + + return ret; +} + +static void unic_uninit_channels_attr(struct unic_dev *unic_dev) +{ + struct unic_channels *channels = &unic_dev->channels; + + mutex_destroy(&channels->mutex); +} + +int unic_init_tx(struct unic_dev *unic_dev, u32 num) +{ + struct unic_channel *c; + u32 i, j; + int ret; + + for (i = 0; i < num; i++) { + ret = unic_create_cq(unic_dev, i, UNIC_CQ_SQ); + if (ret) { + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to init tx cq(%u), ret=%d.\n", i, ret); + goto err_create_cq; + } + } + + for (j = 0; j < num; j++) { + ret = unic_create_sq(unic_dev, j); + if (ret) { + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to init tx sq(%u), ret=%d.\n", j, ret); + goto err_create_sq; + } + c = &unic_dev->channels.c[j]; + c->sq->cq = c->sq_cq; + } + + set_bit(UNIC_TX_INITED, &unic_dev->channels.state); + + return 0; + +err_create_sq: + unic_destroy_sq(unic_dev, j); +err_create_cq: + unic_destroy_cq(unic_dev, i, UNIC_CQ_SQ); + return ret; +} + +int unic_init_rx(struct unic_dev *unic_dev, u32 num) +{ + struct unic_channel *c; + u32 i, j; + int ret; + + for (i = 0; i < num; i++) { + ret = unic_create_cq(unic_dev, i, UNIC_CQ_RQ); + if (ret) { + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to init rx cq(%u), ret=%d.\n", i, ret); + goto err_create_cq; + } + } + + for (j = 0; j < num; j++) { + ret = unic_create_rq(unic_dev, j); + if (ret) { + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to init rx rq(%u), ret=%d.\n", j, ret); + goto err_create_rq; + } + c = &unic_dev->channels.c[j]; + c->rq->cq = c->rq_cq; + } + + set_bit(UNIC_RX_INITED, &unic_dev->channels.state); + + return 0; + +err_create_rq: + unic_destroy_rq(unic_dev, j); +err_create_cq: + unic_destroy_cq(unic_dev, i, UNIC_CQ_RQ); + return ret; +} + +static int unic_init_jetty(struct unic_dev *unic_dev, u32 num) +{ + int ret; + + ret = unic_init_rx(unic_dev, num); + if (ret) + return ret; + + ret = unic_init_tx(unic_dev, num); + if (ret) + unic_destroy_rx(unic_dev, num); + + return ret; +} + +void unic_destroy_tx(struct unic_dev *unic_dev, u32 num) +{ + if (!test_and_clear_bit(UNIC_TX_INITED, &unic_dev->channels.state)) + return; + + unic_destroy_sq(unic_dev, num); + unic_destroy_cq(unic_dev, num, UNIC_CQ_SQ); +} + +void unic_destroy_rx(struct unic_dev *unic_dev, u32 num) +{ + if (!test_and_clear_bit(UNIC_RX_INITED, &unic_dev->channels.state)) + return; + + unic_destroy_rq(unic_dev, num); + unic_destroy_cq(unic_dev, num, UNIC_CQ_RQ); +} + +static void unic_destroy_jetty(struct unic_dev *unic_dev, u32 num) +{ + unic_destroy_tx(unic_dev, num); + unic_destroy_rx(unic_dev, num); +} + +static inline int unic_init_rss(struct unic_dev *unic_dev) +{ + return unic_set_rss_tc_mode(unic_dev, UNIC_RSS_TC_VALID); +} + +static int __unic_init_channels(struct unic_dev *unic_dev, u32 channels_num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channels *channels = &unic_dev->channels; + int ret; + u32 i; + + channels->num = channels_num; + channels->c = kcalloc(channels_num, sizeof(*channels->c), GFP_KERNEL); + if (ZERO_OR_NULL_PTR(channels->c)) + return -ENOMEM; + + ret = unic_init_jetty(unic_dev, channels->num); + if (ret) { + dev_err(adev->dev.parent, + "failed to init jetty, ret = %d.\n", ret); + goto err_init_jetty; + } + + for (i = 0; i < channels->num; i++) + netif_napi_add(unic_dev->comdev.netdev, &channels->c[i].napi, + unic_napi_poll); + + ret = unic_init_rss(unic_dev); + if (ret) { + dev_err(adev->dev.parent, + "failed to init rss, ret = %d.\n", ret); + goto err_init_rss; + } + + return 0; + +err_init_rss: + for (; i > 0; i--) + netif_napi_del(&channels->c[i - 1].napi); + unic_destroy_jetty(unic_dev, channels->num); + +err_init_jetty: + kfree(channels->c); + channels->c = NULL; + + return ret; +} + +int unic_init_channels(struct unic_dev *unic_dev, u32 channels_num) +{ + struct unic_channels *channels = &unic_dev->channels; + int ret; + + mutex_lock(&channels->mutex); + + ret = __unic_init_channels(unic_dev, channels_num); + if (!ret) + clear_bit(UNIC_STATE_CHANNEL_INVALID, &unic_dev->state); + + mutex_unlock(&channels->mutex); + return ret; +} + +static inline void unic_uninit_rss(struct unic_dev *unic_dev) +{ + unic_set_rss_tc_mode(unic_dev, UNIC_RSS_TC_INVALID); +} + +static void __unic_uninit_channels(struct unic_dev *unic_dev) +{ + struct unic_channels *channels = &unic_dev->channels; + u32 i; + + unic_uninit_rss(unic_dev); + + if (!channels->c) + return; + + for (i = 0; i < channels->num; i++) + netif_napi_del(&channels->c[i].napi); + + unic_destroy_jetty(unic_dev, channels->num); + + kfree(channels->c); + + channels->c = NULL; +} + +void unic_uninit_channels(struct unic_dev *unic_dev) +{ + struct unic_channels *channels = &unic_dev->channels; + + mutex_lock(&channels->mutex); + + set_bit(UNIC_STATE_CHANNEL_INVALID, &unic_dev->state); + __unic_uninit_channels(unic_dev); + + mutex_unlock(&channels->mutex); +} + +static void unic_set_netdev_attr(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + netdev->watchdog_timeo = UNIC_WATCHDOG_TIMEOUT; + netdev->priv_flags |= IFF_UNICAST_FLT; + + if (unic_dev_ubl_supported(unic_dev)) { + netdev->features |= NETIF_F_VLAN_CHALLENGED; + netdev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + } + + if (unic_dev_tx_csum_offload_supported(unic_dev)) + netdev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; + + if (unic_dev_rx_csum_offload_supported(unic_dev)) + netdev->features |= NETIF_F_RXCSUM; + + netdev->hw_features |= netdev->features; + + netdev->vlan_features |= netdev->features & + ~NETIF_F_HW_VLAN_CTAG_FILTER; +} + +static int unic_dev_init_mtu(struct unic_dev *unic_dev) +{ + struct net_device *netdev = unic_dev->comdev.netdev; + struct unic_caps *caps = &unic_dev->caps; + int ret; + + netdev->mtu = UB_DATA_LEN; + netdev->max_mtu = caps->max_trans_unit; + netdev->min_mtu = caps->min_trans_unit; + + ret = unic_config_mtu(unic_dev, netdev->mtu); + if (ret == -EPERM) + return 0; + + return ret; +} + +static int unic_init_mac(struct unic_dev *unic_dev) +{ + struct unic_mac *mac = &unic_dev->hw.mac; + int ret; + + mac->duplex = DUPLEX_FULL; + mac->link_status = UNIC_LINK_STATUS_DOWN; + + ret = unic_set_mac_speed_duplex(unic_dev, mac->speed, mac->duplex, + mac->lanes); + if (ret && ret != -EPERM) + return ret; + + ret = unic_set_mac_autoneg(unic_dev, mac->autoneg); + if (ret && ret != -EPERM) + return ret; + + ret = unic_dev_fec_supported(unic_dev) && mac->user_fec_mode ? + unic_set_fec_mode(unic_dev, mac->user_fec_mode) : 0; + if (ret) + return ret; + + ret = unic_dev_init_mtu(unic_dev); + if (ret) { + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to set initial mtu, ret = %d.\n", ret); + return ret; + } + + return 0; +} + +int unic_set_mtu(struct unic_dev *unic_dev, int new_mtu) +{ + u16 max_frame_size; + int ret; + + new_mtu = max(new_mtu, UB_DATA_LEN); + + ret = unic_check_validate_dump_mtu(unic_dev, new_mtu, &max_frame_size); + if (ret == -EPERM) { + return 0; + } else if (ret < 0) { + unic_err(unic_dev, "invalid MTU(%d), please check, ret = %d.\n", + new_mtu, ret); + return -EINVAL; + } + + ret = unic_config_mtu(unic_dev, new_mtu); + if (ret) + unic_err(unic_dev, + "failed to change MTU to %d, ret = %d.\n", new_mtu, ret); + + return ret; +} + +static void unic_update_stats_for_all(struct unic_dev *unic_dev) +{ + if (!unic_dev_ubl_supported(unic_dev) && + unic_dev_fec_stats_supported(unic_dev) && + unic_dev->hw.mac.fec_mode != ETHTOOL_FEC_OFF) + unic_update_fec_stats(unic_dev); +} + +static void unic_task_schedule(struct unic_dev *unic_dev, + unsigned long delay_time) +{ + if (!test_bit(UNIC_STATE_REMOVING, &unic_dev->state)) + mod_delayed_work(unic_wq, &unic_dev->service_task, delay_time); +} + +static void unic_periodic_service_task(struct unic_dev *unic_dev) +{ +#define UNIC_UPDATE_STATS_TIMER_INTERVAL 300UL + unsigned long delta = round_jiffies_relative(HZ); + + unic_link_status_update(unic_dev); + unic_update_port_info(unic_dev); + unic_sync_rack_ip_table(unic_dev); + unic_sync_promisc_mode(unic_dev); + + if (!(unic_dev->serv_processed_cnt % UNIC_UPDATE_STATS_TIMER_INTERVAL)) + unic_update_stats_for_all(unic_dev); + + unic_dev->serv_processed_cnt++; + unic_task_schedule(unic_dev, delta); +} + +static void unic_service_task(struct work_struct *work) +{ + struct unic_dev *unic_dev = + container_of(work, struct unic_dev, service_task.work); + + unic_periodic_service_task(unic_dev); +} + +static void unic_init_vport_info(struct unic_dev *unic_dev) +{ + unic_dev->vport.back = unic_dev; + + INIT_LIST_HEAD(&unic_dev->vport.addr_tbl.tmp_ip_list); + spin_lock_init(&unic_dev->vport.addr_tbl.tmp_ip_lock); + INIT_LIST_HEAD(&unic_dev->vport.addr_tbl.ip_list); + spin_lock_init(&unic_dev->vport.addr_tbl.ip_list_lock); +} + +static int unic_alloc_vport_buf(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + u8 i; + + for (i = 0; i < unic_dev->caps.vport_buf_num; i++) { + unic_dev->vbuf[i].buf = dma_alloc_coherent(adev->dev.parent, + unic_dev->caps.vport_buf_size, + &unic_dev->vbuf[i].dma_addr, GFP_KERNEL); + if (!unic_dev->vbuf[i].buf) { + dev_err(adev->dev.parent, + "failed to alloc vport buffer.\n"); + goto alloc_err; + } + } + + return 0; + +alloc_err: + for (; i > 0; i--) + dma_free_coherent(adev->dev.parent, + unic_dev->caps.vport_buf_size, + unic_dev->vbuf[i - 1].buf, unic_dev->vbuf[i - 1].dma_addr); + return -ENOMEM; +} + +static void unic_free_vport_buf(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + u32 i; + + for (i = 0; i < unic_dev->caps.vport_buf_num; i++) + dma_free_coherent(adev->dev.parent, + unic_dev->caps.vport_buf_size, + unic_dev->vbuf[i].buf, unic_dev->vbuf[i].dma_addr); +} + +static int unic_init_vport_buf(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + int ret; + + if (!unic_dev->caps.vport_buf_size || !unic_dev->caps.vport_buf_num) + return 0; + + if (unic_dev->caps.vport_buf_num > UNIC_MAX_VPORT_BUF_NUM) { + dev_err(adev->dev.parent, + "vport_buf_num exceeded the maximum(%d).\n", + UNIC_MAX_VPORT_BUF_NUM); + return -EINVAL; + } + + ret = unic_alloc_vport_buf(unic_dev); + if (ret) + return ret; + + ret = unic_cfg_vport_buf(unic_dev, true); + if (ret) + unic_free_vport_buf(unic_dev); + + return ret; +} + +static void unic_uninit_vport_buf(struct unic_dev *unic_dev) +{ + if (!unic_dev->caps.vport_buf_size || !unic_dev->caps.vport_buf_num) + return; + + unic_cfg_vport_buf(unic_dev, false); + unic_free_vport_buf(unic_dev); +} + +static int unic_init_vport(struct unic_dev *unic_dev) +{ + int ret; + + ret = unic_init_vport_buf(unic_dev); + if (ret) + return ret; + + unic_init_vport_info(unic_dev); + + return ret; +} + +static void unic_uninit_vport(struct unic_dev *unic_dev) +{ + unic_uninit_rack_ip_table(unic_dev); + unic_uninit_vport_buf(unic_dev); +} + +static void unic_init_netdev_info(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + unic_set_netdev_attr(netdev); + unic_set_netdev_ops(netdev); + unic_set_ethtool_ops(netdev); + unic_set_dcbnl_ops(netdev); + + SET_NETDEV_DEV(netdev, unic_dev->comdev.adev->dev.parent); +} + +static int unic_init_dev_addr(struct unic_dev *unic_dev) +{ + if (unic_dev_ubl_supported(unic_dev)) + return unic_init_guid(unic_dev); + + return 0; +} + +static int unic_init_netdev_priv(struct net_device *netdev, + struct auxiliary_device *adev) +{ + struct unic_dev *priv = netdev_priv(netdev); + int ret; + + priv->comdev.netdev = netdev; + priv->comdev.adev = adev; + priv->msg_enable = netif_msg_init(netif_debug, DEFAULT_MSG_LEVEL); + priv->tid = ubase_get_dev_caps(adev)->tid; + mutex_init(&priv->act_info.mutex); + + ret = unic_query_dev_res(priv); + if (ret) + goto destroy_lock; + + ret = unic_init_vport(priv); + if (ret) + goto destroy_lock; + + ret = unic_update_port_info(priv); + if (ret) { + dev_err(adev->dev.parent, + "failed to update port info, ret = %d.\n", ret); + goto err_uninit_vport; + } + + ret = unic_init_mac(priv); + if (ret) + goto err_uninit_vport; + + ret = unic_init_dev_addr(priv); + if (ret) + goto err_uninit_vport; + + ret = unic_init_channels_attr(priv); + if (ret) + goto err_uninit_vport; + + ret = unic_init_channels(priv, priv->channels.num); + if (ret) { + dev_err(adev->dev.parent, + "failed to init channels, ret = %d.\n", ret); + goto err_uninit_channels_attr; + } + + set_bit(UNIC_STATE_DOWN, &priv->state); + return 0; + +err_uninit_channels_attr: + unic_uninit_channels_attr(priv); +err_uninit_vport: + unic_uninit_vport(priv); +destroy_lock: + mutex_destroy(&priv->act_info.mutex); + + return ret; +} + +static void unic_uninit_netdev_priv(struct net_device *netdev) +{ + struct unic_dev *priv = netdev_priv(netdev); + + unic_uninit_channels(priv); + unic_uninit_channels_attr(priv); + unic_uninit_vport(priv); + mutex_destroy(&priv->act_info.mutex); +} + +int unic_init_wq(void) +{ + unic_wq = alloc_workqueue("%s", WQ_UNBOUND, 0, "unic"); + if (!unic_wq) + return -ENOMEM; + + return 0; +} + +void unic_destroy_wq(void) +{ + destroy_workqueue(unic_wq); +} + +static void unic_init_period_task(struct net_device *netdev) +{ + struct unic_dev *priv = netdev_priv(netdev); + + INIT_DELAYED_WORK(&priv->service_task, unic_service_task); +} + +void unic_start_period_task(struct net_device *netdev) +{ + unsigned long delta = round_jiffies_relative(HZ); + struct unic_dev *unic_dev = netdev_priv(netdev); + + unic_task_schedule(unic_dev, delta); +} + +void unic_remove_period_task(struct unic_dev *unic_dev) +{ + if (unic_dev->service_task.work.func) + cancel_delayed_work_sync(&unic_dev->service_task); +} + +int unic_change_rss_size(struct unic_dev *unic_dev, u32 new_rss_size, + u32 org_rss_size) +{ + struct unic_channels *channels = &unic_dev->channels; + int ret; + + dev_info(unic_dev->comdev.adev->dev.parent, + "change rss_size from %u to %u.\n", org_rss_size, new_rss_size); + + mutex_lock(&channels->mutex); + __unic_uninit_channels(unic_dev); + + channels->rss_size = new_rss_size; + channels->num = channels->rss_size * channels->rss_vl_num; + + unic_update_queue_info(unic_dev); + + ret = __unic_init_channels(unic_dev, channels->num); + if (ret) + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to change rss_size, ret = %d.\n", ret); + + mutex_unlock(&channels->mutex); + + return ret; +} + +static struct net_device *unic_alloc_netdev(struct auxiliary_device *adev) +{ + struct ubase_caps *caps = ubase_get_dev_caps(adev); + u32 channel_num = unic_channels_max_num(adev); + struct net_device *netdev = NULL; + char name[IFNAMSIZ] = {0}; + + if (!channel_num) + channel_num = UNIC_DEFAULT_CHANNEL_NUM; + + if (ubase_adev_ubl_supported(adev)) { +#ifdef CONFIG_UB_UNIC_UBL + snprintf(name, IFNAMSIZ, "ublc%ud%ue%u", caps->chip_id, + caps->die_id, caps->ue_id); + netdev = alloc_netdev_mq(sizeof(struct unic_dev), name, + NET_NAME_USER, ubl_setup, channel_num); +#else + dev_warn(adev->dev.parent, + "failed to alloc netdev because of ubl macro is not enabled.\n"); +#endif + } + + return netdev; +} + +static void unic_start_dev_period_task(struct net_device *netdev) +{ + unic_init_period_task(netdev); + unic_start_period_task(netdev); +} + +int unic_dev_init(struct auxiliary_device *adev) +{ + struct net_device *netdev; + int ret; + + netdev = unic_alloc_netdev(adev); + if (!netdev) + return -ENOMEM; + + dev_set_drvdata(&adev->dev, netdev_priv(netdev)); + + ret = unic_init_netdev_priv(netdev, adev); + if (ret) { + dev_err(adev->dev.parent, + "failed to init netdev_priv, ret = %d.\n", ret); + goto err_free_netdev; + } + unic_init_netdev_info(netdev); + + ret = unic_register_event(adev); + if (ret) + goto err_uninit_netdev_priv; + + ret = register_netdev(netdev); + if (ret) { + dev_err(adev->dev.parent, + "failed to register netdev, ret = %d.\n", ret); + goto err_unregister_event; + } + + unic_query_rack_ip(adev); + unic_start_dev_period_task(netdev); + + return 0; + +err_unregister_event: + unic_unregister_event(adev); +err_uninit_netdev_priv: + unic_uninit_netdev_priv(netdev); +err_free_netdev: + free_netdev(netdev); + return ret; +} + +void unic_dev_uninit(struct auxiliary_device *adev) +{ + struct unic_dev *priv = (struct unic_dev *)dev_get_drvdata(&adev->dev); + struct net_device *netdev = priv->comdev.netdev; + struct unic_promisc_en promisc_en = {0}; + + /* cancel service task and wait it finish before release resources. */ + unic_remove_period_task(priv); + + /* Ensure that the link status change is processed before + * unregister_netdev. Prevent the kernel from accessing the released + * netdev address. + */ + unic_unregister_event(adev); + + /* Explicitly disable promisc to avoid hardware promisc residue */ + unic_set_promisc_mode(priv, &promisc_en); + + if (netdev->reg_state != NETREG_UNINITIALIZED) + unregister_netdev(netdev); + + unic_uninit_netdev_priv(netdev); + + free_netdev(netdev); + + dev_set_drvdata(&adev->dev, NULL); +} diff --git a/drivers/net/ub/unic/unic_dev.h b/drivers/net/ub/unic/unic_dev.h new file mode 100644 index 0000000000000000000000000000000000000000..f0df795ad011f879b248822a3afefd2be5668a72 --- /dev/null +++ b/drivers/net/ub/unic/unic_dev.h @@ -0,0 +1,357 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_DEV_H__ +#define __UNIC_DEV_H__ + +#include +#include +#include +#include +#include +#include + +#include "unic.h" +#include "unic_cmd.h" +#include "unic_ethtool.h" +#include "unic_rx.h" +#include "unic_tx.h" +#include "unic_txrx.h" + +#define UNIC_MOD_VERSION "1.0" +#define UNIC_AE_LEVEL_NUM 1 + +enum unic_dev_state { + UNIC_STATE_INVALID, + UNIC_STATE_RESETTING, + UNIC_STATE_INITED, + UNIC_STATE_DOWN, + UNIC_STATE_REMOVING, + UNIC_STATE_DISABLED, + UNIC_STATE_LINK_UPDATING, + UNIC_STATE_TESTING, + UNIC_STATE_FEC_STATS_UPDATING, + UNIC_STATE_MAC_STATS_UPDATING, + UNIC_STATE_CHANNEL_INVALID, + UNIC_STATE_DEACTIVATE, +}; + +enum unic_vport_state { + UNIC_VPORT_STATE_ALIVE, + UNIC_VPORT_STATE_PROMISC_CHANGE, + UNIC_VPORT_STATE_IP_TBL_CHANGE, + UNIC_VPORT_STATE_IP_QUERYING, +}; + +enum unic_channel_state { + UNIC_TX_CHANGED, + UNIC_RX_CHANGED, + UNIC_TX_INITED, + UNIC_RX_INITED, +}; + +#define UNIC_CQE_PERIOD_0 0 +#define UNIC_CQE_PERIOD_4 4 +#define UNIC_CQE_PERIOD_16 16 +#define UNIC_CQE_PERIOD_64 64 +#define UNIC_CQE_PERIOD_256 256 +#define UNIC_CQE_PERIOD_1024 1024 +#define UNIC_CQE_PERIOD_4096 4096 +#define UNIC_CQE_PERIOD_16384 16384 +#define UNIC_CQE_PERIOD_ERR 16385 + +#define UNIC_DEFAULT_CHANNEL_NUM 1 + +#define unic_dbg(_unic_dev, fmt, ...) \ + do { \ + if (unic_dbg_log()) \ + netdev_info((_unic_dev)->comdev.netdev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__); \ + } while (0) + +#define unic_err(_unic_dev, fmt, ...) \ + netdev_err((_unic_dev)->comdev.netdev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__) + +#define unic_info(_unic_dev, fmt, ...) \ + netdev_info((_unic_dev)->comdev.netdev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__) + +#define unic_warn(_unic_dev, fmt, ...) \ + netdev_warn((_unic_dev)->comdev.netdev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__) + +struct unic_mac { + u8 port; /* see PORT_TP/PORT_FIBRE... */ + u8 media_type; /* port media type, see unic_media_type */ + u8 module_type; /* module type, see unic_module_type */ + u8 duplex; + u8 autoneg; + u8 support_autoneg; + u32 link_status; /* link status of mac & phy (if phy exists) */ + u32 speed; + u32 max_speed; + u32 speed_ability; + u32 lanes; /* lane number */ + u32 fec_mode; /* active fec mode */ + u32 fec_ability; /* supported fec mode */ + u32 user_fec_mode; /* user configured fec mode */ + u8 mac_addr[ETH_ALEN]; + + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising); +}; + +struct unic_hw { + struct unic_mac mac; +}; + +struct unic_channel { + struct unic_sq *sq; + struct unic_rq *rq; + struct unic_cq *sq_cq; + struct unic_cq *rq_cq; + struct napi_struct napi; + /*​ ​monitor each jetty's return status in parallel operations.​ */ + int ret; +}; + +struct unic_coal_txrx { + struct unic_coalesce tx_coal; + struct unic_coalesce rx_coal; +}; + +struct unic_vl { + u8 vl_num; + u8 dscp_app_cnt; + u8 dscp_prio[UBASE_MAX_DSCP]; + u8 vl_tsa[UBASE_MAX_VL_NUM]; + u8 prio_vl[UNIC_MAX_PRIO_NUM]; + u8 vl_bw[UBASE_MAX_VL_NUM]; + u16 queue_count[UBASE_MAX_VL_NUM]; + u16 queue_offset[UBASE_MAX_VL_NUM]; + u8 vl_sl[UBASE_MAX_VL_NUM]; + u64 vl_maxrate[UBASE_MAX_VL_NUM]; + u16 vl_bitmap; +}; + +struct unic_channels { + struct unic_channel *c; + struct unic_vl vl; + u32 num; + u16 sqebb_depth; + u16 rqe_depth; + u32 sq_cqe_depth; + u32 rq_cqe_depth; + u16 rx_buff_len; + u16 cqe_size; + u32 rss_size; + u8 rss_vl_num; /* the number of vl used by unic */ + u8 sqebb_shift; + u8 sq_jfc_shift; + u8 rq_jfc_shift; + struct unic_coal_txrx unic_coal; + struct mutex mutex; /* protects modify channel's resource and mapping */ + unsigned long state; +}; + +struct unic_caps { + u16 rx_buff_len; + u16 total_ip_tbl_size; + u32 rsvd0[5]; + u16 max_trans_unit; + u16 min_trans_unit; + u32 vport_buf_size; /* unit: byte */ + u8 vport_buf_num; + u16 max_int_ql; /* max value of interrupt coalesce based on INT_QL */ + u16 max_int_gl; /* max value of interrupt coalesce based on INT_GL */ +}; + +struct unic_fec_stats_item { + u64 corr_blocks; + u64 uncorr_blocks; + u64 corr_bits; +}; + +struct unic_fec_stats { + u8 lane_num; + struct unic_fec_stats_item total; + struct unic_fec_stats_item lane[UNIC_FEC_STATS_MAX_LANE]; +}; + +struct unic_stats { + struct unic_fec_stats fec_stats; +}; + +struct unic_addr_tbl { + spinlock_t ip_list_lock; /* protect ip address need to add/detele */ + struct list_head ip_list; /* Store ip table */ + + spinlock_t tmp_ip_lock; /* protect ip address from controller */ + struct list_head tmp_ip_list; /* Store temprary ip table */ +}; + +struct unic_vport_buf { + void *buf; + dma_addr_t dma_addr; +}; + +struct unic_vport { + struct unic_dev *back; + struct unic_addr_tbl addr_tbl; + u8 overflow_promisc_flags; + u8 last_promisc_flags; + unsigned long state; +}; + +struct unic_act_info { + bool deactivate; + struct mutex mutex; /* protects modify deactivate state */ +}; + +struct unic_dev { + /* This member must be first, adaptor driver will relay on it */ + struct ubase_adev_com comdev; + struct unic_hw hw; + struct unic_channels channels; + struct ubase_dbgfs dbgfs; + u8 dcbx_cap; + struct unic_caps caps; + u32 cap_bits[UNIC_CAP_LEN]; + u32 msg_enable; + unsigned long state; + struct delayed_work service_task; + struct ubase_event_nb ae_nbs[UNIC_AE_LEVEL_NUM]; + struct unic_stats stats; + u8 netdev_flags; + u8 loopback_flags; + struct unic_vport vport; + struct unic_vport_buf vbuf[UNIC_MAX_VPORT_BUF_NUM]; + unsigned long serv_processed_cnt; + struct unic_act_info act_info; + u32 tid; + u8 sw_link_status; +}; + +int unic_dev_init(struct auxiliary_device *adev); +void unic_dev_uninit(struct auxiliary_device *adev); +int unic_set_mtu(struct unic_dev *unic_dev, int new_mtu); +u32 unic_channels_max_num(struct auxiliary_device *adev); +u32 unic_get_max_rss_size(struct unic_dev *unic_dev); +int unic_init_channels(struct unic_dev *unic_dev, u32 channels_num); +void unic_uninit_channels(struct unic_dev *unic_dev); +void unic_start_period_task(struct net_device *netdev); +void unic_remove_period_task(struct unic_dev *unic_dev); +int unic_init_wq(void); +void unic_destroy_wq(void); +int unic_init_rx(struct unic_dev *unic_dev, u32 num); +int unic_init_tx(struct unic_dev *unic_dev, u32 num); +void unic_destroy_rx(struct unic_dev *unic_dev, u32 num); +void unic_destroy_tx(struct unic_dev *unic_dev, u32 num); +int unic_change_rss_size(struct unic_dev *unic_dev, u32 new_rss_size, + u32 org_rss_size); +int unic_set_vl_map(struct unic_dev *unic_dev, u8 *dscp_prio, u8 *prio_vl, + u8 map_type); +int unic_dbg_log(void); + +static inline bool unic_dev_ubl_supported(struct unic_dev *unic_dev) +{ + return ubase_adev_ubl_supported(unic_dev->comdev.adev); +} + +static inline bool unic_dev_ets_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_ETS_B); +} + +static inline bool unic_dev_fec_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_FEC_B); +} + +static inline bool unic_dev_rss_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_RSS_B); +} + +static inline bool unic_dev_tc_speed_limit_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_TC_SPEED_LIMIT_B); +} + +static inline bool unic_dev_tx_csum_offload_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_TX_CSUM_OFFLOAD_B); +} + +static inline bool unic_dev_rx_csum_offload_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_RX_CSUM_OFFLOAD_B); +} + +static inline bool unic_dev_fec_stats_supported(struct unic_dev *unic_dev) +{ + return unic_get_cap_bit(unic_dev, UNIC_SUPPORT_FEC_STATS_B); +} + +static inline bool __unic_removing(struct unic_dev *unic_dev) +{ + return test_bit(UNIC_STATE_REMOVING, &unic_dev->state); +} + +static inline bool __unic_resetting(struct unic_dev *unic_dev) +{ + return test_bit(UNIC_STATE_RESETTING, &unic_dev->state); +} + +static inline bool unic_resetting(struct net_device *netdev) +{ + return __unic_resetting(netdev_priv(netdev)); +} + +static inline bool __unic_initing(struct unic_dev *unic_dev) +{ + return !test_bit(UNIC_STATE_INITED, &unic_dev->state); +} + +static inline bool unic_initing(struct net_device *netdev) +{ + return __unic_initing(netdev_priv(netdev)); +} + +static inline bool unic_is_initing_or_resetting(struct unic_dev *unic_dev) +{ + return __unic_resetting(unic_dev) || __unic_initing(unic_dev); +} + +static inline u32 unic_get_sq_cqe_mask(struct unic_dev *unic_dev) +{ + return unic_dev->channels.sq_cqe_depth - 1; +} + +static inline u32 unic_get_rq_cqe_mask(struct unic_dev *unic_dev) +{ + return unic_dev->channels.rq_cqe_depth - 1; +} + +static inline bool unic_tx_changed(struct unic_dev *unic_dev) +{ + return test_bit(UNIC_TX_CHANGED, &unic_dev->channels.state); +} + +static inline bool unic_rx_changed(struct unic_dev *unic_dev) +{ + return test_bit(UNIC_RX_CHANGED, &unic_dev->channels.state); +} + +static inline u32 unic_cmd_timeout(struct unic_dev *unic_dev) +{ +#define UNIC_CMD_TIMEOUT 5000 + + return __unic_removing(unic_dev) ? UNIC_CMD_TIMEOUT : 0; +} + +#endif diff --git a/drivers/net/ub/unic/unic_ethtool.c b/drivers/net/ub/unic/unic_ethtool.c new file mode 100644 index 0000000000000000000000000000000000000000..9e50f0332d4e6759c74b8b243ad1e08371d7f30d --- /dev/null +++ b/drivers/net/ub/unic/unic_ethtool.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include + +#include "unic.h" +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_netdev.h" +#include "unic_stats.h" +#include "unic_channel.h" +#include "unic_ethtool.h" + +static u32 unic_get_link_status(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + unic_link_status_update(unic_dev); + + return unic_dev->sw_link_status; +} + +static int unic_get_link_ksettings(struct net_device *netdev, + struct ethtool_link_ksettings *cmd) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + /* Ensure that the latest information is obtained. */ + unic_update_port_info(unic_dev); + + return 0; +} + +static void unic_get_driver_info(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + struct ubase_caps *caps = ubase_get_dev_caps(unic_dev->comdev.adev); + u32 fw_version = 0; + + if (!caps) + unic_err(unic_dev, + "failed to get fw version, use default value 0.\n"); + else + fw_version = caps->fw_version; + + strscpy(drvinfo->version, UNIC_MOD_VERSION, sizeof(drvinfo->version)); + strscpy(drvinfo->driver, unic_dev->comdev.adev->name, + sizeof(drvinfo->driver)); + strscpy(drvinfo->bus_info, dev_name(unic_dev->comdev.adev->dev.parent), + sizeof(drvinfo->bus_info)); + + snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), "%u.%u.%u.%u", + u32_get_bits(fw_version, UBASE_FW_VERSION_BYTE3_MASK), + u32_get_bits(fw_version, UBASE_FW_VERSION_BYTE2_MASK), + u32_get_bits(fw_version, UBASE_FW_VERSION_BYTE1_MASK), + u32_get_bits(fw_version, UBASE_FW_VERSION_BYTE0_MASK)); +} + +static int unic_get_fecparam(struct net_device *ndev, + struct ethtool_fecparam *fec) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + struct unic_mac *mac = &unic_dev->hw.mac; + + if (!unic_dev_fec_supported(unic_dev)) + return -EOPNOTSUPP; + + fec->fec = mac->fec_ability; + fec->active_fec = mac->fec_mode; + + return 0; +} + +static int unic_set_fecparam(struct net_device *ndev, + struct ethtool_fecparam *fec) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + struct unic_mac *mac = &unic_dev->hw.mac; + u32 fec_mode; + int ret; + + if (!unic_dev_fec_supported(unic_dev)) + return -EOPNOTSUPP; + + fec_mode = fec->fec; + if (!(mac->fec_ability & fec_mode)) { + unic_err(unic_dev, + "unsupported fec mode, fec mode = %u.\n", fec_mode); + return -EINVAL; + } + + if (mac->fec_mode == fec_mode) + return 0; + + if (netif_msg_ifdown(unic_dev)) + unic_info(unic_dev, "set fec mode = %u.\n", fec_mode); + + ret = unic_set_fec_mode(unic_dev, fec_mode); + if (ret) + return ret; + + mac->user_fec_mode = fec_mode; + + return 0; +} + +#define UNIC_ETHTOOL_RING (ETHTOOL_RING_USE_RX_BUF_LEN | \ + ETHTOOL_RING_USE_TX_PUSH) +#define UNIC_ETHTOOL_COALESCE (ETHTOOL_COALESCE_USECS | \ + ETHTOOL_COALESCE_USE_ADAPTIVE | \ + ETHTOOL_COALESCE_MAX_FRAMES) + +static const struct ethtool_ops unic_ethtool_ops = { + .supported_ring_params = UNIC_ETHTOOL_RING, + .cap_link_lanes_supported = true, + .supported_coalesce_params = UNIC_ETHTOOL_COALESCE, + .get_link = unic_get_link_status, + .get_link_ksettings = unic_get_link_ksettings, + .get_drvinfo = unic_get_driver_info, + .get_channels = unic_get_channels, + .set_channels = unic_set_channels, + .get_ringparam = unic_get_channels_param, + .set_ringparam = unic_set_channels_param, + .get_fecparam = unic_get_fecparam, + .set_fecparam = unic_set_fecparam, + .get_fec_stats = unic_get_fec_stats, +}; + +void unic_set_ethtool_ops(struct net_device *netdev) +{ + netdev->ethtool_ops = &unic_ethtool_ops; +} diff --git a/drivers/net/ub/unic/unic_ethtool.h b/drivers/net/ub/unic/unic_ethtool.h new file mode 100644 index 0000000000000000000000000000000000000000..4ba10f0b54b2d3ce90d251c3b84af230749f94ca --- /dev/null +++ b/drivers/net/ub/unic/unic_ethtool.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_ETHTOOL_H__ +#define __UNIC_ETHTOOL_H__ + +#include +#include + +#define UNIC_TXRX_MIN_DEPTH 64 + +struct unic_reset_type_map { + enum ethtool_reset_flags reset_flags; + enum ubase_reset_type reset_type; +}; + +struct unic_coalesce { + u16 int_gl; + u16 int_ql; + u8 adapt_enable : 1; + u8 resv : 7; +}; + +void unic_set_ethtool_ops(struct net_device *netdev); + +#endif diff --git a/drivers/net/ub/unic/unic_event.c b/drivers/net/ub/unic/unic_event.c new file mode 100644 index 0000000000000000000000000000000000000000..bcd1e210f447753bf9a587bf62c8f384b097a176 --- /dev/null +++ b/drivers/net/ub/unic/unic_event.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unic_cmd.h" +#include "unic_crq.h" +#include "unic_dcbnl.h" +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_netdev.h" +#include "unic_qos_hw.h" +#include "unic_rack_ip.h" +#include "unic_event.h" + +int unic_comp_handler(struct notifier_block *nb, unsigned long jfcn, void *data) +{ + struct auxiliary_device *adev = (struct auxiliary_device *)data; + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + struct unic_channels *channels = &unic_dev->channels; + struct unic_cq *cq; + u32 index; + + if (test_bit(UNIC_STATE_CHANNEL_INVALID, &unic_dev->state)) + return -EBUSY; + + index = jfcn < channels->num ? jfcn : jfcn - channels->num; + if (index >= channels->num) + return -EINVAL; + + if (jfcn > channels->num) + cq = channels->c[index].rq->cq; + else + cq = channels->c[index].sq->cq; + + cq->event_cnt++; + + napi_schedule(&channels->c[index].napi); + + return 0; +} + +static struct ubase_ctrlq_event_nb unic_ctrlq_events[] = { + { + .service_type = UBASE_CTRLQ_SER_TYPE_IP_ACL, + .opcode = UBASE_CTRLQ_OPC_NOTIFY_IP, + .crq_handler = unic_handle_notify_ip_event, + } +}; + +static void unic_unregister_ctrlq_event(struct auxiliary_device *adev, + u32 ctrlq_crq_event_num) +{ + u32 i; + + for (i = 0; i < ctrlq_crq_event_num; i++) + ubase_ctrlq_unregister_crq_event(adev, + unic_ctrlq_events[i].service_type, + unic_ctrlq_events[i].opcode); +} + +static int unic_register_ctrlq_event(struct auxiliary_device *adev) +{ + int ret; + u32 i; + + for (i = 0; i < ARRAY_SIZE(unic_ctrlq_events); i++) { + unic_ctrlq_events[i].back = adev; + ret = ubase_ctrlq_register_crq_event(adev, &unic_ctrlq_events[i]); + if (ret) { + dev_err(adev->dev.parent, + "failed to register ctrlq event[%u], ret = %d.\n", + i, ret); + unic_unregister_ctrlq_event(adev, i); + return ret; + } + } + + return 0; +} + +static struct ubase_crq_event_nb unic_crq_events[] = { + { + .opcode = UBASE_OPC_QUERY_LINK_STATUS, + .crq_handler = unic_handle_link_status_event, + }, +}; + +static void unic_unregister_crq_event(struct auxiliary_device *adev, + u32 crq_event_num) +{ + u32 i; + + for (i = 0; i < crq_event_num; i++) + ubase_unregister_crq_event(adev, unic_crq_events[i].opcode); +} + +static int unic_register_crq_event(struct auxiliary_device *adev) +{ + int ret; + u32 i; + + for (i = 0; i < ARRAY_SIZE(unic_crq_events); i++) { + unic_crq_events[i].back = adev; + + ret = ubase_register_crq_event(adev, &unic_crq_events[i]); + if (ret) { + dev_err(adev->dev.parent, + "failed to register crq event[%u], ret = %d.\n", + i, ret); + unic_unregister_crq_event(adev, i); + return ret; + } + } + + return 0; +} + +int unic_register_event(struct auxiliary_device *adev) +{ + int ret; + + ret = unic_register_crq_event(adev); + if (ret) + return ret; + + ret = unic_register_ctrlq_event(adev); + if (ret) + goto unregister_crq; + + return 0; + +unregister_crq: + unic_unregister_crq_event(adev, ARRAY_SIZE(unic_crq_events)); + return ret; +} + +void unic_unregister_event(struct auxiliary_device *adev) +{ + unic_unregister_ctrlq_event(adev, ARRAY_SIZE(unic_ctrlq_events)); + unic_unregister_crq_event(adev, ARRAY_SIZE(unic_crq_events)); +} diff --git a/drivers/net/ub/unic/unic_event.h b/drivers/net/ub/unic/unic_event.h new file mode 100644 index 0000000000000000000000000000000000000000..cc863c3b60df147635ad7fd774de818cd38e5d94 --- /dev/null +++ b/drivers/net/ub/unic/unic_event.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_EVENT_H__ +#define __UNIC_EVENT_H__ + +#include + +int unic_register_event(struct auxiliary_device *adev); +void unic_unregister_event(struct auxiliary_device *adev); +int unic_comp_handler(struct notifier_block *nb, unsigned long jfcn, void *data); + +#endif diff --git a/drivers/net/ub/unic/unic_guid.c b/drivers/net/ub/unic/unic_guid.c new file mode 100644 index 0000000000000000000000000000000000000000..5639ab540eee2df5caf79cfc1a39498c433c13f5 --- /dev/null +++ b/drivers/net/ub/unic/unic_guid.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#ifdef CONFIG_UB_UNIC_UBL +#include +#endif +#include + +#include "unic_guid.h" + +#ifndef UBL_ALEN +#define UBL_ALEN 16 +#endif + +static bool unic_is_valid_net_guid(u8 *guid) +{ +#define UNIC_MGUID_PREFIX_LEN 14 + + u8 invalid_guid[UBL_ALEN]; + + memset(invalid_guid, 0, UBL_ALEN); + if (!memcmp(guid, invalid_guid, UBL_ALEN)) + return false; + + memset(invalid_guid, 0xff, UBL_ALEN); + if (!memcmp(guid, invalid_guid, UNIC_MGUID_PREFIX_LEN)) + return false; + + return true; +} + +static inline void unic_guid_le_to_net_trans(u8 *src_guid, u8 *dest_guid) +{ + int i; + + for (i = 0; i < UBL_ALEN; i++) + dest_guid[i] = src_guid[UBL_ALEN - i - 1]; +} + +static void unic_query_net_guid(struct unic_dev *unic_dev, u8 *guid) +{ + struct unic_query_net_guid_cmd resp = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_NET_GUID, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_NET_GUID, true, + sizeof(resp), &resp); + ret = ubase_cmd_send_inout(unic_dev->comdev.adev, &in, &out); + if (ret) { + dev_warn(unic_dev->comdev.adev->dev.parent, + "failed to query net guid, ret = %d.\n", ret); + return; + } + + memcpy(guid, resp.guid, UBL_ALEN); +} + +static void unic_get_net_guid(struct unic_dev *unic_dev, u8 *guid) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + u8 out_guid[UBL_ALEN] = {0}; + bool is_random = false; + + unic_query_net_guid(unic_dev, out_guid); + + unic_guid_le_to_net_trans(out_guid, guid); + + while (unlikely(!unic_is_valid_net_guid(guid))) { + get_random_bytes(guid, UBL_ALEN); + is_random = true; + } + + if (unlikely(is_random)) + dev_warn(adev->dev.parent, + "using random GUID %02x:%02x:...:%02x:%02x.\n", + guid[0], guid[1], + guid[UBL_ALEN - 2], guid[UBL_ALEN - 1]); +} + +int unic_init_guid(struct unic_dev *unic_dev) +{ + struct net_device *netdev = unic_dev->comdev.netdev; + u8 guid[UBL_ALEN]; + + unic_get_net_guid(unic_dev, guid); + + dev_addr_set(netdev, guid); + memcpy(netdev->perm_addr, guid, netdev->addr_len); + + return 0; +} diff --git a/drivers/net/ub/unic/unic_guid.h b/drivers/net/ub/unic/unic_guid.h new file mode 100644 index 0000000000000000000000000000000000000000..2f8f436db04171a72897e3385bc9961e8b93a147 --- /dev/null +++ b/drivers/net/ub/unic/unic_guid.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_GUID_H__ +#define __UNIC_GUID_H__ + +#include "unic_dev.h" + +int unic_init_guid(struct unic_dev *unic_dev); + +#endif diff --git a/drivers/net/ub/unic/unic_hw.c b/drivers/net/ub/unic/unic_hw.c new file mode 100644 index 0000000000000000000000000000000000000000..d2b7026514c460dc9f579b3b30e0665a71e70a28 --- /dev/null +++ b/drivers/net/ub/unic/unic_hw.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include + +#include "unic.h" +#include "unic_cmd.h" +#include "unic_dev.h" +#include "unic_hw.h" + +static const struct unic_speed_bit_map speed_bit_map[] = { + /* caution: should keep them in descending order by speed! + * see unic_get_max_speed() + */ + {UNIC_MAC_SPEED_400G, UNIC_LANES_8, UNIC_SUPPORT_400G_X8_BIT}, + {UNIC_MAC_SPEED_400G, UNIC_LANES_4, UNIC_SUPPORT_400G_X4_BIT}, + {UNIC_MAC_SPEED_200G, UNIC_LANES_4, UNIC_SUPPORT_200G_X4_BIT}, + {UNIC_MAC_SPEED_200G, UNIC_LANES_2, UNIC_SUPPORT_200G_X2_BIT}, + {UNIC_MAC_SPEED_100G, UNIC_LANES_4, UNIC_SUPPORT_100G_X4_BIT}, + {UNIC_MAC_SPEED_100G, UNIC_LANES_2, UNIC_SUPPORT_100G_X2_BIT}, + {UNIC_MAC_SPEED_100G, UNIC_LANES_1, UNIC_SUPPORT_100G_X1_BIT}, + {UNIC_MAC_SPEED_50G, UNIC_LANES_2, UNIC_SUPPORT_50G_X2_BIT}, + {UNIC_MAC_SPEED_50G, UNIC_LANES_1, UNIC_SUPPORT_50G_X1_BIT}, + {UNIC_MAC_SPEED_25G, UNIC_LANES_1, UNIC_SUPPORT_25G_X1_BIT}, + {UNIC_MAC_SPEED_10G, UNIC_LANES_1, UNIC_SUPPORT_10G_X1_BIT}, +}; + +static int unic_get_port_info(struct unic_dev *unic_dev) +{ + struct unic_query_port_info_resp resp = {0}; + struct unic_mac *mac = &unic_dev->hw.mac; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_PORT_INFO, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_PORT_INFO, false, + sizeof(resp), &resp); + + ret = ubase_cmd_send_inout(unic_dev->comdev.adev, &in, &out); + if (ret) + return ret; + + mac->speed = le32_to_cpu(resp.speed); + mac->speed_ability = le32_to_cpu(resp.speed_ability); + mac->autoneg = resp.autoneg; + mac->support_autoneg = resp.autoneg_ability; + mac->lanes = resp.lanes; + mac->module_type = resp.module_type; + mac->fec_mode = le16_to_cpu(resp.fec_mode); + mac->fec_ability = le16_to_cpu(resp.fec_ability); + + return 0; +} + +int unic_set_mac_autoneg(struct unic_dev *unic_dev, u8 autoneg) +{ + struct unic_cfg_autoneg_mode_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + if (unic_dev_ubl_supported(unic_dev)) + return unic_is_initing_or_resetting(unic_dev) ? 0 : -EOPNOTSUPP; + + if (!unic_dev->hw.mac.support_autoneg) + return 0; + + req.autoneg_en = autoneg ? 1 : 0; + + ubase_fill_inout_buf(&in, UBASE_OPC_CONFIG_AUTONEG_MODE, false, + sizeof(req), &req); + + ret = ubase_cmd_send_in(unic_dev->comdev.adev, &in); + if (ret && ret != -EPERM) + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to send cmd in config autoneg(%u), ret = %d.\n", + autoneg, ret); + + if (!ret) + unic_dev->hw.mac.autoneg = req.autoneg_en; + + return ret; +} + +int unic_set_mac_speed_duplex(struct unic_dev *unic_dev, u32 speed, u8 duplex, + u8 lanes) +{ + struct unic_cfg_speed_dup_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + if (unic_dev_ubl_supported(unic_dev)) + return unic_is_initing_or_resetting(unic_dev) ? 0 : -EOPNOTSUPP; + + req.speed = cpu_to_le32(speed); + req.duplex = duplex; + req.lanes = lanes; + + ubase_fill_inout_buf(&in, UBASE_OPC_CONFIG_SPEED_DUP, false, + sizeof(req), &req); + + ret = ubase_cmd_send_in(unic_dev->comdev.adev, &in); + if (ret && ret != -EPERM) + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to send cmd in config speed(%u), ret = %d.\n", + speed, ret); + + return ret; +} + +static void unic_set_fec_ability(struct unic_mac *mac) +{ + linkmode_clear_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, mac->supported); + linkmode_clear_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, mac->supported); + linkmode_clear_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, mac->supported); + linkmode_clear_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, mac->supported); + + if (mac->fec_ability & ETHTOOL_FEC_RS) + linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, + mac->supported); + if (mac->fec_ability & ETHTOOL_FEC_OFF) + linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, + mac->supported); +} + +static const struct unic_link_mode_bit_map sr_linkmode_bit_map[] = { + {UNIC_SUPPORT_400G_X8_BIT, ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT}, + {UNIC_SUPPORT_400G_X4_BIT, ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT}, + {UNIC_SUPPORT_200G_X4_BIT, ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT}, + {UNIC_SUPPORT_200G_X2_BIT, ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT}, + {UNIC_SUPPORT_100G_X4_BIT, ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT}, + {UNIC_SUPPORT_100G_X2_BIT, ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT}, + {UNIC_SUPPORT_100G_X1_BIT, ETHTOOL_LINK_MODE_100000baseSR_Full_BIT}, + {UNIC_SUPPORT_50G_X2_BIT, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT}, + {UNIC_SUPPORT_50G_X1_BIT, ETHTOOL_LINK_MODE_50000baseSR_Full_BIT}, + {UNIC_SUPPORT_25G_X1_BIT, ETHTOOL_LINK_MODE_25000baseSR_Full_BIT}, + {UNIC_SUPPORT_10G_X1_BIT, ETHTOOL_LINK_MODE_10000baseSR_Full_BIT}, +}; + +static const struct unic_link_mode_bit_map lr_linkmode_bit_map[] = { + {UNIC_SUPPORT_400G_X8_BIT, ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT}, + {UNIC_SUPPORT_400G_X4_BIT, ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT}, + {UNIC_SUPPORT_200G_X4_BIT, ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT}, + {UNIC_SUPPORT_200G_X2_BIT, ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT}, + {UNIC_SUPPORT_100G_X4_BIT, ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT}, + {UNIC_SUPPORT_100G_X2_BIT, ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT}, + {UNIC_SUPPORT_100G_X1_BIT, ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT}, + {UNIC_SUPPORT_50G_X1_BIT, ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT}, + {UNIC_SUPPORT_10G_X1_BIT, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT}, +}; + +static const struct unic_link_mode_bit_map cr_linkmode_bit_map[] = { + {UNIC_SUPPORT_400G_X8_BIT, ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT}, + {UNIC_SUPPORT_400G_X4_BIT, ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT}, + {UNIC_SUPPORT_200G_X4_BIT, ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT}, + {UNIC_SUPPORT_200G_X2_BIT, ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT}, + {UNIC_SUPPORT_100G_X4_BIT, ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT}, + {UNIC_SUPPORT_100G_X2_BIT, ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT}, + {UNIC_SUPPORT_100G_X1_BIT, ETHTOOL_LINK_MODE_100000baseCR_Full_BIT}, + {UNIC_SUPPORT_50G_X2_BIT, ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT}, + {UNIC_SUPPORT_50G_X1_BIT, ETHTOOL_LINK_MODE_50000baseCR_Full_BIT}, + {UNIC_SUPPORT_25G_X1_BIT, ETHTOOL_LINK_MODE_25000baseCR_Full_BIT}, + {UNIC_SUPPORT_10G_X1_BIT, ETHTOOL_LINK_MODE_10000baseCR_Full_BIT}, +}; + +static const struct unic_link_mode_bit_map kr_linkmode_bit_map[] = { + {UNIC_SUPPORT_400G_X8_BIT, ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT}, + {UNIC_SUPPORT_400G_X4_BIT, ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT}, + {UNIC_SUPPORT_200G_X4_BIT, ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT}, + {UNIC_SUPPORT_200G_X2_BIT, ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT}, + {UNIC_SUPPORT_100G_X4_BIT, ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT}, + {UNIC_SUPPORT_100G_X2_BIT, ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT}, + {UNIC_SUPPORT_100G_X1_BIT, ETHTOOL_LINK_MODE_100000baseKR_Full_BIT}, + {UNIC_SUPPORT_50G_X2_BIT, ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT}, + {UNIC_SUPPORT_50G_X1_BIT, ETHTOOL_LINK_MODE_50000baseKR_Full_BIT}, + {UNIC_SUPPORT_25G_X1_BIT, ETHTOOL_LINK_MODE_25000baseKR_Full_BIT}, + {UNIC_SUPPORT_10G_X1_BIT, ETHTOOL_LINK_MODE_10000baseKR_Full_BIT}, +}; + +static void unic_set_linkmode(const struct unic_link_mode_bit_map *map, u32 map_size, + u32 speed_ability, unsigned long *link_mode) +{ + u32 i; + + for (i = 0; i < map_size; i++) { + if (speed_ability & map[i].speed_bit) + linkmode_set_bit(map[i].link_mode, link_mode); + } +} + +static void unic_set_linkmode_sr(u32 speed_ability, unsigned long *link_mode) +{ + unic_set_linkmode(sr_linkmode_bit_map, ARRAY_SIZE(sr_linkmode_bit_map), + speed_ability, link_mode); +} + +static void unic_set_linkmode_lr(u32 speed_ability, unsigned long *link_mode) +{ + unic_set_linkmode(lr_linkmode_bit_map, ARRAY_SIZE(lr_linkmode_bit_map), + speed_ability, link_mode); +} + +static void unic_set_linkmode_cr(u32 speed_ability, unsigned long *link_mode) +{ + unic_set_linkmode(cr_linkmode_bit_map, ARRAY_SIZE(cr_linkmode_bit_map), + speed_ability, link_mode); +} + +static void unic_set_linkmode_kr(u32 speed_ability, unsigned long *link_mode) +{ + unic_set_linkmode(kr_linkmode_bit_map, ARRAY_SIZE(kr_linkmode_bit_map), + speed_ability, link_mode); +} + +static void unic_update_speed_advertising(struct unic_mac *mac) +{ + u32 speed_ability = mac->speed_ability; + + switch (mac->module_type) { + case UNIC_MODULE_TYPE_FIBRE_LR: + unic_set_linkmode_lr(speed_ability, mac->advertising); + break; + case UNIC_MODULE_TYPE_FIBRE_SR: + case UNIC_MODULE_TYPE_AOC: + unic_set_linkmode_sr(speed_ability, mac->advertising); + break; + case UNIC_MODULE_TYPE_CR: + unic_set_linkmode_cr(speed_ability, mac->advertising); + break; + case UNIC_MODULE_TYPE_KR: + unic_set_linkmode_kr(speed_ability, mac->advertising); + break; + default: + break; + } +} + +static void unic_update_fec_advertising(struct unic_mac *mac) +{ + if (mac->fec_mode & ETHTOOL_FEC_RS) + linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, + mac->advertising); + else if (mac->fec_mode & ETHTOOL_FEC_OFF) + linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, + mac->advertising); +} + +static void unic_update_advertising(struct unic_dev *unic_dev) +{ + struct unic_mac *mac = &unic_dev->hw.mac; + + linkmode_zero(mac->advertising); + + unic_update_speed_advertising(mac); + unic_update_fec_advertising(mac); +} + +static void unic_update_port_capability(struct unic_dev *unic_dev) +{ + struct unic_mac *mac = &unic_dev->hw.mac; + + unic_set_fec_ability(mac); + + if (mac->support_autoneg) { + linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, mac->supported); + linkmode_copy(mac->advertising, mac->supported); + } else { + linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, mac->supported); + unic_update_advertising(unic_dev); + } +} + +int unic_update_port_info(struct unic_dev *unic_dev) +{ + int ret; + + ret = unic_get_port_info(unic_dev); + if (ret) + return ret; + + unic_update_port_capability(unic_dev); + + return 0; +} + +static void unic_setup_promisc_req(struct unic_promisc_cfg_cmd *req, + struct unic_promisc_en *promisc_en) +{ + req->promisc_uc_ind = 1; + req->promisc_rx_uc_guid_en = promisc_en->en_uc_guid; + req->promisc_rx_uc_ip_en = promisc_en->en_uc_ip; + + req->promisc_mc_ind = 1; + req->promisc_rx_mc_en = promisc_en->en_mc; +} + +int unic_get_promisc_mode(struct unic_dev *unic_dev, + struct unic_promisc_cfg_cmd *resp) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_PROMISC_MODE, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_CFG_PROMISC_MODE, true, + sizeof(*resp), resp); + + ret = ubase_cmd_send_inout(adev, &in, &out); + if (ret) + dev_err(adev->dev.parent, + "failed to query promisc mode, ret = %d.\n", ret); + + return ret; +} + +int unic_set_promisc_mode(struct unic_dev *unic_dev, + struct unic_promisc_en *promisc_en) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_promisc_cfg_cmd req = {0}; + struct ubase_cmd_buf in; + u32 time_out; + int ret; + + unic_setup_promisc_req(&req, promisc_en); + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_PROMISC_MODE, false, + sizeof(req), &req); + + time_out = unic_cmd_timeout(unic_dev); + ret = ubase_cmd_send_in_ex(adev, &in, time_out); + if (ret == -EPERM) + return 0; + else if (ret) + unic_err(unic_dev, + "failed to set promisc mode, ret = %d.\n", ret); + + return ret; +} + +void unic_fill_promisc_en(struct unic_promisc_en *promisc_en, u8 flags) +{ + promisc_en->en_uc_ip = !!(flags & UNIC_UPE); + promisc_en->en_mc = !!(flags & UNIC_MPE); +} + +int unic_activate_promisc_mode(struct unic_dev *unic_dev, bool activate) +{ + struct unic_promisc_en promisc_en = {0}; + u8 flags; + + flags = unic_dev->netdev_flags | unic_dev->vport.last_promisc_flags; + if (!flags) + return 0; + + if (activate) + unic_fill_promisc_en(&promisc_en, flags); + + return unic_set_promisc_mode(unic_dev, &promisc_en); +} + +int unic_sync_promisc_mode(struct unic_dev *unic_dev) +{ + struct unic_act_info *act_info = &unic_dev->act_info; + struct unic_vport *vport = &unic_dev->vport; + struct unic_promisc_en promisc_en; + struct auxiliary_device *adev; + int ret = 0; + + adev = unic_dev->comdev.adev; + + if (!mutex_trylock(&act_info->mutex)) + return 0; + + if (act_info->deactivate) + goto out; + + if (vport->last_promisc_flags != vport->overflow_promisc_flags) { + set_bit(UNIC_VPORT_STATE_PROMISC_CHANGE, &vport->state); + vport->last_promisc_flags = vport->overflow_promisc_flags; + } + + if (!test_and_clear_bit(UNIC_VPORT_STATE_PROMISC_CHANGE, &vport->state)) + goto out; + + unic_fill_promisc_en(&promisc_en, + unic_dev->netdev_flags | vport->last_promisc_flags); + + ret = unic_set_promisc_mode(unic_dev, &promisc_en); + if (ret) + set_bit(UNIC_VPORT_STATE_PROMISC_CHANGE, &vport->state); + +out: + mutex_unlock(&act_info->mutex); + + return ret; +} + +static void unic_parse_fiber_link_mode(struct unic_dev *unic_dev, + u32 speed_ability) +{ + struct unic_mac *mac = &unic_dev->hw.mac; + + unic_set_linkmode_sr(speed_ability, mac->supported); + unic_set_linkmode_lr(speed_ability, mac->supported); + unic_set_linkmode_cr(speed_ability, mac->supported); + + linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, mac->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, mac->supported); +} + +static void unic_parse_backplane_link_mode(struct unic_dev *unic_dev, + u32 speed_ability) +{ + struct unic_mac *mac = &unic_dev->hw.mac; + + unic_set_linkmode_kr(speed_ability, mac->supported); + + linkmode_set_bit(ETHTOOL_LINK_MODE_Backplane_BIT, mac->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, mac->supported); +} + +static void unic_parse_link_mode(struct unic_dev *unic_dev, u32 speed_ability) +{ + u8 media_type = unic_dev->hw.mac.media_type; + + if (media_type == UNIC_MEDIA_TYPE_FIBER) + unic_parse_fiber_link_mode(unic_dev, speed_ability); + else if (media_type == UNIC_MEDIA_TYPE_BACKPLANE) + unic_parse_backplane_link_mode(unic_dev, speed_ability); + else + dev_warn(unic_dev->comdev.adev->dev.parent, + "unknown media_type = %u.\n", media_type); +} + +static u32 unic_get_max_speed(u32 cur_speed, u32 speed_ability) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(speed_bit_map); i++) { + if (speed_ability & speed_bit_map[i].speed_bit) + return speed_bit_map[i].speed; + } + + return cur_speed; +} + +static void unic_parse_mac_cfg(struct unic_dev *unic_dev, + const struct unic_res_cmd_resp *resp) +{ + struct unic_mac *mac = &unic_dev->hw.mac; + + mac->media_type = resp->media_type; + mac->speed = le32_to_cpu(resp->default_speed); + mac->lanes = resp->default_lanes; + mac->speed_ability = le32_to_cpu(resp->speed_ability); + + unic_parse_link_mode(unic_dev, mac->speed_ability); + + mac->max_speed = unic_get_max_speed(mac->speed, mac->speed_ability); + + unic_dbg(unic_dev, + "default speed = %u, lanes = %u, speed_ability = 0x%x\n", + mac->speed, mac->lanes, mac->speed_ability); +} + +static void unic_parse_dev_caps(struct unic_dev *unic_dev, + const struct unic_res_cmd_resp *resp) +{ +#define KB 1024U + + struct unic_caps *caps = &unic_dev->caps; + int i; + + for (i = 0; i < UNIC_CAP_LEN; i++) + unic_dev->cap_bits[i] = le32_to_cpu(resp->cap_bits[i]); + + caps->rx_buff_len = le16_to_cpu(resp->rx_buff_len); + caps->total_ip_tbl_size = le16_to_cpu(resp->total_ip_tbl_size); + caps->max_trans_unit = le16_to_cpu(resp->max_trans_unit); + caps->min_trans_unit = le16_to_cpu(resp->min_trans_unit); + caps->vport_buf_size = le16_to_cpu(resp->vport_buf_size) * KB; + caps->vport_buf_num = resp->vport_buf_num; + caps->max_int_ql = le16_to_cpu(resp->max_int_ql); + caps->max_int_gl = le16_to_cpu(resp->max_int_gl); + + unic_parse_mac_cfg(unic_dev, resp); +} + +int unic_query_dev_res(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_res_cmd_resp resp; + struct ubase_cmd_buf out; + struct ubase_cmd_buf in; + int ret; + + memset(&resp, 0, sizeof(resp)); + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_NIC_RSRC_PARAM, true, 0, + NULL); + + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_NIC_RSRC_PARAM, false, + sizeof(resp), &resp); + + ret = ubase_cmd_send_inout(adev, &in, &out); + if (ret) { + dev_err(adev->dev.parent, "failed to query unic res, ret = %d.\n", + ret); + return ret; + } + + unic_parse_dev_caps(unic_dev, &resp); + + return 0; +} + +int unic_check_validate_dump_mtu(struct unic_dev *unic_dev, int new_mtu, + u16 *max_frame_size) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_config_max_trans_unit_cmd resp = {0}; + struct unic_config_max_trans_unit_cmd req = {0}; + struct ubase_cmd_buf out; + struct ubase_cmd_buf in; + int ret; + + req.max_trans_unit = cpu_to_le16(new_mtu); + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_MTU, true, sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_CFG_MTU, true, sizeof(resp), &resp); + + ret = ubase_cmd_send_inout(adev, &in, &out); + if (!ret) + *max_frame_size = le16_to_cpu(resp.max_trans_unit); + + return ret; +} + +int unic_config_mtu(struct unic_dev *unic_dev, int new_mtu) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_config_max_trans_unit_cmd req = {0}; + struct ubase_cmd_buf in; + + req.max_trans_unit = cpu_to_le16(new_mtu); + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_MTU, false, sizeof(req), &req); + + return ubase_cmd_send_in(adev, &in); +} + +static int unic_query_flush_status(struct unic_dev *unic_dev, u8 *status) +{ + struct unic_query_flush_status_resp resp = {0}; + struct ubase_cmd_buf out; + struct ubase_cmd_buf in; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_FLUSH_STATUS, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_FLUSH_STATUS, false, + sizeof(resp), &resp); + + ret = ubase_cmd_send_inout(unic_dev->comdev.adev, &in, &out); + if (ret) { + if (ret == -EPERM) + return -EOPNOTSUPP; + + unic_err(unic_dev, + "failed to send cmd when query flush status, ret = %d.\n", + ret); + return ret; + } + + *status = resp.status; + return ret; +} + +static int unic_wait_hw_queue_flush_done(struct unic_dev *unic_dev) +{ +#define UNIC_FLUSH_STATUS_WAIT_CNT 10 +#define UNIC_FLUSH_STATUS_WAIT_MS 50 +#define UNIC_FLUSH_DONE 1 + + int i, ret; + u8 status; + + for (i = 0; i < UNIC_FLUSH_STATUS_WAIT_CNT; i++) { + ret = unic_query_flush_status(unic_dev, &status); + if (ret) { + msleep(UNIC_FLUSH_STATUS_WAIT_MS); + continue; + } + + if (status == UNIC_FLUSH_DONE) + return 0; + + msleep(UNIC_FLUSH_STATUS_WAIT_MS); + } + + return -EBUSY; +} + +int unic_mac_cfg(struct unic_dev *unic_dev, bool enable) +{ + struct unic_ld_config_mode_cmd req = {0}; + unsigned long dl_conf_mode_en = 0; + struct ubase_cmd_buf in; + u32 time_out; + int ret; + + if (enable) { + set_bit(UNIC_DL_CONFIG_MODE_TX_EN_B, &dl_conf_mode_en); + set_bit(UNIC_DL_CONFIG_MODE_RX_EN_B, &dl_conf_mode_en); + } + + if (test_bit(UNIC_STATE_TESTING, &unic_dev->state) && enable) + set_bit(UNIC_DL_CONFIG_MODE_APP_LP_EN_B, &dl_conf_mode_en); + + req.txrx_en = cpu_to_le32((u32)dl_conf_mode_en); + + ubase_fill_inout_buf(&in, UBASE_OPC_DL_CONFIG_MODE, false, + sizeof(req), &req); + + time_out = unic_cmd_timeout(unic_dev); + ret = ubase_cmd_send_in_ex(unic_dev->comdev.adev, &in, time_out); + if (ret) + unic_err(unic_dev, "failed to %s mac, ret = %d.\n", + enable ? "enable" : "disable", ret); + + return ret; +} + +int unic_mac_mode_cfg(struct net_device *netdev, bool enable) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + int ret = 0; + + if (!unic_dev_ubl_supported(unic_dev)) + ret = unic_mac_cfg(unic_dev, enable); + + if (!enable && !unic_resetting(netdev)) + return unic_wait_hw_queue_flush_done(unic_dev); + + return ret; +} + +int unic_cfg_vport_buf(struct unic_dev *unic_dev, bool init) +{ + struct unic_cfg_vport_buf_cmd req = {0}; + struct ubase_cmd_buf in; + dma_addr_t addr; + u32 time_out; + int ret, i; + + req.buf_num = unic_dev->caps.vport_buf_num; + if (init) { + for (i = 0; i < unic_dev->caps.vport_buf_num; i++) { + addr = unic_dev->vbuf[i].dma_addr; + req.buf_addr[i * U32S_PER_U64] = cpu_to_le32(lower_32_bits(addr)); + req.buf_addr[i * U32S_PER_U64 + 1] = cpu_to_le32(upper_32_bits(addr)); + } + } + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_VPORT_BUF, false, + sizeof(req), &req); + time_out = unic_cmd_timeout(unic_dev); + ret = ubase_cmd_send_in_ex(unic_dev->comdev.adev, &in, time_out); + if (ret) + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to config vport buffer, ret = %d.\n", ret); + return ret; +} + +int unic_set_fec_mode(struct unic_dev *unic_dev, u32 fec_mode) +{ + struct unic_cfg_fec_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + req.fec_mode = cpu_to_le32(fec_mode); + ubase_fill_inout_buf(&in, UBASE_OPC_CONFIG_FEC_MODE, false, + sizeof(req), &req); + ret = ubase_cmd_send_in(unic_dev->comdev.adev, &in); + if (ret) + dev_err(unic_dev->comdev.adev->dev.parent, + "failed to set fec mode(%u), ret = %d.\n", + fec_mode, ret); + + return ret; +} + +static void unic_fetch_fec_stats(struct unic_dev *unic_dev, + struct unic_fec_stats_item *unic_stats_item, + struct unic_query_fec_stats_item *resp_item) +{ +#define FETCH_FEC_STATS(high, low) (((u64)le32_to_cpu(high) << 32) | \ + (u64)le32_to_cpu(low)) + + if (unic_dev_ubl_supported(unic_dev)) { + unic_stats_item->corr_blocks = + FETCH_FEC_STATS(resp_item->corr_blocks_h, + resp_item->corr_blocks_l); + unic_stats_item->uncorr_blocks = + FETCH_FEC_STATS(resp_item->uncorr_blocks_h, + resp_item->uncorr_blocks_l); + unic_stats_item->corr_bits = + FETCH_FEC_STATS(resp_item->corr_bits_h, + resp_item->corr_bits_l); + } else { + unic_stats_item->corr_blocks += + FETCH_FEC_STATS(resp_item->corr_blocks_h, + resp_item->corr_blocks_l); + unic_stats_item->uncorr_blocks += + FETCH_FEC_STATS(resp_item->uncorr_blocks_h, + resp_item->uncorr_blocks_l); + unic_stats_item->corr_bits += + FETCH_FEC_STATS(resp_item->corr_bits_h, + resp_item->corr_bits_l); + } +} + +int unic_update_fec_stats(struct unic_dev *unic_dev) +{ + struct unic_query_fec_stats_resp resp = {0}; + struct unic_fec_stats *fec_stats; + struct ubase_cmd_buf in, out; + u8 i, lane_num; + int ret; + + if (test_and_set_bit(UNIC_STATE_FEC_STATS_UPDATING, &unic_dev->state)) + return -EBUSY; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_FEC_STATS, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_FEC_STATS, false, + sizeof(resp), &resp); + + ret = ubase_cmd_send_inout(unic_dev->comdev.adev, &in, &out); + if (ret) { + unic_err(unic_dev, + "failed to query fec stats, ret = %d.\n", ret); + goto err_send_cmd; + } + + fec_stats = &unic_dev->stats.fec_stats; + fec_stats->lane_num = resp.lane_num; + unic_fetch_fec_stats(unic_dev, &fec_stats->total, &resp.total); + lane_num = min_t(u8, fec_stats->lane_num, UNIC_FEC_STATS_MAX_LANE); + for (i = 0; i < lane_num; i++) + unic_fetch_fec_stats(unic_dev, &fec_stats->lane[i], + &resp.lane[i]); + +err_send_cmd: + clear_bit(UNIC_STATE_FEC_STATS_UPDATING, &unic_dev->state); + return ret; +} + +static void unic_set_rss_tc0_param(struct unic_channels *channels, + u16 jfr_cnt, __le16 *jfr_idx) +{ + struct unic_vl *vl = &channels->vl; + u16 jfr_index; + u16 i; + + if (!vl->queue_count[0]) + return; + + for (i = 0; i < jfr_cnt; i++) { + jfr_index = vl->queue_offset[0] + + i % vl->queue_count[0]; + jfr_idx[i] = cpu_to_le16(jfr_index); + } +} + +static void unic_set_rss_multi_tc_param(struct auxiliary_device *adev, + struct unic_channels *channels, + u16 jfr_cnt, __le16 *jfr_idx) +{ + struct ubase_adev_caps *unic_caps = ubase_get_unic_caps(adev); + struct unic_vl *vl = &channels->vl; + u16 jfr_index; + u16 queue_num; + u16 j, k = 0; + u16 i; + + queue_num = (u16)unic_caps->jfr.max_cnt / channels->rss_vl_num; + for (i = 0; i < channels->rss_vl_num; i++) { + if (!vl->queue_count[i]) + return; + for (j = 0; j < queue_num && k < jfr_cnt; j++, k++) { + jfr_index = vl->queue_offset[i] + + j % vl->queue_count[i]; + jfr_idx[k] = cpu_to_le16(jfr_index); + } + } +} + +int unic_set_rss_tc_mode(struct unic_dev *unic_dev, u8 tc_vaild) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channels *channels = &unic_dev->channels; + enum ubase_reset_stage reset_stage; + struct ubase_adev_caps *unic_caps; + struct unic_cfg_rss_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + reset_stage = ubase_get_reset_stage(adev); + if (reset_stage == UBASE_RESET_STAGE_UNINIT) + return -EBUSY; + + unic_caps = ubase_get_unic_caps(adev); + + req.tc_vaild = tc_vaild; + req.tc_mode = channels->rss_vl_num <= 1 ? UNIC_RSS_TC_MODE0 : + UNIC_RSS_TC_MODE1; + req.jfr_reg_num = min(unic_caps->jfr.max_cnt, UNIC_RSS_MAX_CNT); + if (req.tc_vaild) { + if (req.tc_mode == UNIC_RSS_TC_MODE0) + unic_set_rss_tc0_param(channels, req.jfr_reg_num, + req.jfr_idx); + else + unic_set_rss_multi_tc_param(adev, channels, + req.jfr_reg_num, + req.jfr_idx); + } + + ubase_fill_inout_buf(&in, UBASE_OPC_TP_RSS_CONFIG, false, sizeof(req), + &req); + ret = ubase_cmd_send_in(adev, &in); + if (ret) + dev_err(adev->dev.parent, "failed to set rss tc mode. ret = %d.\n", + ret); + + return ret; +} + +int unic_query_rss_cfg(struct unic_dev *unic_dev, struct unic_cfg_rss_cmd *resp) +{ + struct unic_cfg_rss_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_TP_RSS_CONFIG, true, sizeof(req), + &req); + ubase_fill_inout_buf(&out, UBASE_OPC_TP_RSS_CONFIG, true, sizeof(*resp), + resp); + ret = ubase_cmd_send_inout(unic_dev->comdev.adev, &in, &out); + if (ret) + unic_err(unic_dev, + "failed to query rss cfg hw, ret = %d.\n", ret); + + return ret; +} diff --git a/drivers/net/ub/unic/unic_hw.h b/drivers/net/ub/unic/unic_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..ebe0c714549ddfa245e9e770eff821c78ca1658b --- /dev/null +++ b/drivers/net/ub/unic/unic_hw.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_HW_H__ +#define __UNIC_HW_H__ + +#include +#include + +#include "unic_dev.h" + +#define UNIC_DL_CONFIG_MODE_TX_EN_B 0 +#define UNIC_DL_CONFIG_MODE_RX_EN_B 1 +#define UNIC_DL_CONFIG_MODE_APP_LP_EN_B 2 + +#define UNIC_LINK_STATUS_UP_B 0 +#define UNIC_LINK_STATUS_UP_M BIT(UNIC_LINK_STATUS_UP_B) + +#define UNIC_LINK_STATUS_DOWN 0 +#define UNIC_LINK_STATUS_UP 1 + +enum unic_rss_tc_mode { + UNIC_RSS_TC_MODE0, + UNIC_RSS_TC_MODE1 +}; + +enum unic_rss_tc_valid { + UNIC_RSS_TC_INVALID, + UNIC_RSS_TC_VALID +}; + +struct unic_caps_item { + void *p; + u32 default_val; + u8 size; + const char *name; +}; + +struct unic_speed_bit_map { + u32 speed; + u32 lanes; + u32 speed_bit; +}; + +struct unic_link_mode_bit_map { + u32 speed_bit; + enum ethtool_link_mode_bit_indices link_mode; +}; + +struct unic_promisc_en { + u8 en_uc_ip; + u8 en_uc_guid; + u8 en_mc; +}; + +#define UNIC_RSS_MAX_CNT 10U +struct unic_cfg_rss_cmd { + u8 tc_mode; + u8 tc_vaild; + u8 jfr_reg_num; + u8 rsvd; + __le16 jfr_idx[UNIC_RSS_MAX_CNT]; +}; + +static inline bool unic_is_port_down(struct unic_dev *unic_dev) +{ + return unic_dev->hw.mac.link_status == UNIC_LINK_STATUS_DOWN; +} + +int unic_update_port_info(struct unic_dev *unic_dev); + +int unic_set_mac_speed_duplex(struct unic_dev *unic_dev, u32 speed, u8 duplex, + u8 lanes); +int unic_set_mac_autoneg(struct unic_dev *unic_dev, u8 autoneg); + +int unic_query_dev_res(struct unic_dev *unic_dev); + +int unic_check_validate_dump_mtu(struct unic_dev *unic_dev, int new_mtu, + u16 *max_frame_size); +int unic_config_mtu(struct unic_dev *unic_dev, int new_mtu); + +int unic_mac_cfg(struct unic_dev *unic_dev, bool enable); +int unic_mac_mode_cfg(struct net_device *netdev, bool enable); + +int unic_sync_promisc_mode(struct unic_dev *unic_dev); +int unic_activate_promisc_mode(struct unic_dev *unic_dev, bool activate); +void unic_fill_promisc_en(struct unic_promisc_en *promisc_en, u8 flags); +int unic_get_promisc_mode(struct unic_dev *unic_dev, + struct unic_promisc_cfg_cmd *resp); +int unic_set_promisc_mode(struct unic_dev *unic_dev, + struct unic_promisc_en *promisc_en); + +int unic_cfg_vport_buf(struct unic_dev *unic_dev, bool init); +int unic_set_fec_mode(struct unic_dev *unic_dev, u32 fec_mode); +int unic_update_fec_stats(struct unic_dev *unic_dev); +int unic_set_rss_tc_mode(struct unic_dev *unic_dev, u8 tc_vaild); +int unic_query_rss_cfg(struct unic_dev *unic_dev, + struct unic_cfg_rss_cmd *resp); + +#endif diff --git a/drivers/net/ub/unic/unic_main.c b/drivers/net/ub/unic/unic_main.c new file mode 100644 index 0000000000000000000000000000000000000000..6c445f4714c54815555777611eecdaccbe266c17 --- /dev/null +++ b/drivers/net/ub/unic/unic_main.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include +#include + +#include "debugfs/unic_debugfs.h" +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_netdev.h" + +static int unic_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct unic_dev *unic_dev; + int ret; + + ret = unic_dev_init(adev); + if (ret) { + dev_err(adev->dev.parent, + "failed to init unic dev, ret = %d.\n", ret); + return ret; + } + + unic_dev = (struct unic_dev *)dev_get_drvdata(&adev->dev); + ret = unic_dbg_init(adev); + if (ret) { + unic_err(unic_dev, + "failed to init unic debugfs, ret = %d.\n", ret); + unic_dev_uninit(adev); + return ret; + } + + set_bit(UNIC_STATE_INITED, &unic_dev->state); + + return 0; +} + +static void unic_remove(struct auxiliary_device *adev) +{ +#define UNIC_RESET_WAIT_TIME 100 + + struct unic_dev *unic_dev = (struct unic_dev *)dev_get_drvdata(&adev->dev); + + while (test_and_set_bit(UNIC_STATE_DISABLED, &unic_dev->state)) + msleep(UNIC_RESET_WAIT_TIME); + + set_bit(UNIC_STATE_REMOVING, &unic_dev->state); + unic_dbg_uninit(adev); + unic_dev_uninit(adev); +} + +static const struct auxiliary_device_id unic_id_table[] = { + { + .name = UBASE_ADEV_NAME ".unic", + }, + {}, +}; + +MODULE_DEVICE_TABLE(auxiliary, unic_id_table); + +static struct auxiliary_driver unic_drv = { + .probe = unic_probe, + .remove = unic_remove, + .name = "unic", + .id_table = unic_id_table, +}; + +static int __init unic_init(void) +{ + int ret; + + ret = unic_init_wq(); + if (ret) { + pr_err("unic: failed to create workqueue.\n"); + return ret; + } + + ret = unic_register_ipaddr_notifier(); + if (ret) + goto err_reg_ip_notifier; + + ret = auxiliary_driver_register(&unic_drv); + if (ret) + goto err_aux_reg; + + return ret; + +err_aux_reg: + unic_unregister_ipaddr_notifier(); +err_reg_ip_notifier: + unic_destroy_wq(); + return ret; +} + +static void __exit unic_exit(void) +{ + unic_unregister_ipaddr_notifier(); + auxiliary_driver_unregister(&unic_drv); + unic_destroy_wq(); +} + +module_init(unic_init); +module_exit(unic_exit); + +MODULE_DESCRIPTION("UNIC: Hisilicon Network Driver"); +MODULE_AUTHOR("Huawei Tech. Co., Ltd."); +MODULE_LICENSE("GPL"); +MODULE_VERSION(UNIC_MOD_VERSION); diff --git a/drivers/net/ub/unic/unic_netdev.c b/drivers/net/ub/unic/unic_netdev.c new file mode 100644 index 0000000000000000000000000000000000000000..d8680ff9f894a9ca43757b727d7d4d0d1a314ebc --- /dev/null +++ b/drivers/net/ub/unic/unic_netdev.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unic_cmd.h" +#include "unic_dev.h" +#include "unic_event.h" +#include "unic_hw.h" +#include "unic_rx.h" +#include "unic_tx.h" +#include "unic_txrx.h" +#include "unic_netdev.h" +#include "unic_rack_ip.h" + +static int unic_netdev_set_tcs(struct net_device *netdev) +{ +#define UNIC_OS_DEFAULT_VL 0 + + struct unic_dev *unic_dev = netdev_priv(netdev); + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channels *channels = &unic_dev->channels; + struct ubase_caps *caps = ubase_get_dev_caps(adev); + struct unic_vl *vl = &channels->vl; + int ret; + u8 i; + + if (!caps) { + unic_err(unic_dev, "failed to get caps info.\n"); + return -ENODATA; + } + + netdev_reset_tc(netdev); + + if (caps->vl_num <= 1) + return 0; + + ret = netdev_set_num_tc(netdev, channels->rss_vl_num); + if (ret) { + unic_err(unic_dev, "failed to set num_tc, ret = %d.\n", ret); + return ret; + } + + for (i = 0; i < channels->rss_vl_num; i++) + netdev_set_tc_queue(netdev, i, vl->queue_count[i], + vl->queue_offset[i]); + + for (i = 0; i < UNIC_MAX_PRIO_NUM; i++) { + if (vl->prio_vl[i] > channels->rss_vl_num) + netdev_set_prio_tc_map(netdev, i, UNIC_OS_DEFAULT_VL); + else + netdev_set_prio_tc_map(netdev, i, vl->prio_vl[i]); + } + + return 0; +} + +static int unic_real_num_queue_set(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + u32 queue_size = unic_dev->channels.num; + int ret; + + if (!queue_size) + queue_size = UNIC_DEFAULT_CHANNEL_NUM; + + ret = unic_netdev_set_tcs(netdev); + if (ret) + return ret; + + ret = netif_set_real_num_tx_queues(netdev, queue_size); + if (ret) { + unic_err(unic_dev, + "failed to set real num tx queues, ret = %d!\n", ret); + return ret; + } + + ret = netif_set_real_num_rx_queues(netdev, queue_size); + if (ret) { + unic_err(unic_dev, + "failed to set real num rx queues, ret = %d!\n", ret); + return ret; + } + + return 0; +} + +void unic_enable_channels(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channel *c; + u32 i; + + mutex_lock(&unic_dev->channels.mutex); + if (!unic_dev->channels.c) + goto out; + + for (i = 0; i < unic_dev->channels.num; i++) { + c = &unic_dev->channels.c[i]; + napi_enable(&c->napi); + } + + ubase_comp_register(adev, unic_comp_handler); + +out: + mutex_unlock(&unic_dev->channels.mutex); +} + +void unic_disable_channels(struct unic_dev *unic_dev) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channel *c; + u32 i; + + mutex_lock(&unic_dev->channels.mutex); + if (!unic_dev->channels.c) + goto out; + + ubase_comp_unregister(adev); + + for (i = 0; i < unic_dev->channels.num; i++) { + c = &unic_dev->channels.c[i]; + napi_disable(&c->napi); + } + +out: + mutex_unlock(&unic_dev->channels.mutex); +} + +static int unic_net_up(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + int ret; + + unic_clear_all_queue(netdev); + + unic_enable_channels(unic_dev); + + ret = unic_mac_mode_cfg(netdev, true); + if (ret) { + unic_disable_channels(unic_dev); + return ret; + } + + unic_dev->sw_link_status = UNIC_LINK_STATUS_DOWN; + + return 0; +} + +static void unic_clear_fec_stats(struct unic_dev *unic_dev) +{ + struct unic_fec_stats *fec_stats = &unic_dev->stats.fec_stats; + + if (!unic_dev_ubl_supported(unic_dev)) + memset(fec_stats, 0, sizeof(*fec_stats)); +} + +void unic_link_status_change(struct net_device *netdev, bool linkup) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + /* stop/wake network interface by deactivate/activate event */ + if (test_bit(UNIC_STATE_DEACTIVATE, &unic_dev->state)) + goto out; + + if (linkup) { + if (!test_bit(UNIC_STATE_TESTING, &unic_dev->state)) { + netif_tx_wake_all_queues(netdev); + netif_carrier_on(netdev); + unic_clear_fec_stats(unic_dev); + } + } else { + netif_carrier_off(netdev); + netif_tx_stop_all_queues(netdev); + } + +out: + if (netif_msg_link(unic_dev)) + unic_info(unic_dev, "%s.\n", linkup ? "link up" : "link down"); +} + +void unic_link_status_update(struct unic_dev *unic_dev) +{ + u8 link_status = UNIC_LINK_STATUS_DOWN; + int ret; + + if (test_and_set_bit(UNIC_STATE_LINK_UPDATING, &unic_dev->state)) + return; + + if (test_bit(UNIC_STATE_DOWN, &unic_dev->state)) + goto ifdown; + + ret = unic_query_link_status(unic_dev, &link_status); + if (ret) { + clear_bit(UNIC_STATE_LINK_UPDATING, &unic_dev->state); + return; + } + + unic_dev->hw.mac.link_status = link_status; + if (unic_dev_ubl_supported(unic_dev)) + link_status = UNIC_LINK_STATUS_UP; + +ifdown: + if (link_status != unic_dev->sw_link_status) { + unic_dev->sw_link_status = link_status; + unic_link_status_change(unic_dev->comdev.netdev, link_status); + } + + clear_bit(UNIC_STATE_LINK_UPDATING, &unic_dev->state); +} + +int unic_net_open(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + int ret; + + if (test_bit(UNIC_STATE_RESETTING, &unic_dev->state)) + return -EBUSY; + + if (!test_bit(UNIC_STATE_DOWN, &unic_dev->state)) { + unic_warn(unic_dev, "net open repeatedly.\n"); + return 0; + } + + netif_carrier_off(netdev); + + /* only cfg mac mode, wake network interface by activate event */ + if (test_bit(UNIC_STATE_DEACTIVATE, &unic_dev->state)) { + ret = unic_mac_mode_cfg(netdev, true); + if (ret) { + unic_err(unic_dev, "failed to cfg mac mode, ret = %d.\n", + ret); + return ret; + } + + clear_bit(UNIC_STATE_DOWN, &unic_dev->state); + return 0; + } + + ret = unic_real_num_queue_set(netdev); + if (ret) { + unic_err(unic_dev, "failed to set real num queue, ret = %d.\n", + ret); + return ret; + } + + ret = unic_net_up(netdev); + if (ret) { + unic_err(unic_dev, "failed to up net, ret = %d.\n", ret); + return ret; + } + + clear_bit(UNIC_STATE_DOWN, &unic_dev->state); + + if (netif_msg_ifup(unic_dev)) + unic_info(unic_dev, "net open.\n"); + + return 0; +} + +int unic_net_open_no_link_change(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + int ret; + + if (unic_resetting(netdev)) + return -EBUSY; + + if (test_bit(UNIC_STATE_DOWN, &unic_dev->state)) + return 0; + + netif_carrier_off(netdev); + + ret = unic_real_num_queue_set(netdev); + if (ret) { + unic_err(unic_dev, "failed to set real num queue, ret = %d.\n", + ret); + return ret; + } + + unic_clear_all_queue(netdev); + unic_enable_channels(unic_dev); + + if (netif_msg_ifup(unic_dev)) + unic_info(unic_dev, "netif open.\n"); + + /* ensure that network interface is awakened when linked up, + * otherwise, it will be handled by periodic tasks + */ + if (unic_dev->sw_link_status == UNIC_LINK_STATUS_UP) { + netif_tx_wake_all_queues(netdev); + netif_carrier_on(netdev); + unic_clear_fec_stats(unic_dev); + } + + return 0; +} + +static void unic_dev_stop(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + unic_mac_mode_cfg(netdev, false); + + unic_link_status_update(unic_dev); +} + +void unic_net_stop_no_link_change(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + if (test_bit(UNIC_STATE_DOWN, &unic_dev->state)) + return; + + if (netif_msg_ifdown(unic_dev)) + unic_info(unic_dev, "netif stop.\n"); + + netif_carrier_off(netdev); + netif_tx_disable(netdev); + netif_tx_stop_all_queues(netdev); + + unic_disable_channels(unic_dev); + unic_clear_all_queue(netdev); + unic_reset_tx_queue(netdev); +} + +int unic_net_stop(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + if (test_and_set_bit(UNIC_STATE_DOWN, &unic_dev->state)) + return 0; + + /* only cfg mac mode, because stop network interface has been done + * by deactivate event + */ + if (test_bit(UNIC_STATE_DEACTIVATE, &unic_dev->state)) { + unic_dev_stop(netdev); + return 0; + } + + if (netif_msg_ifdown(unic_dev)) + unic_info(unic_dev, "net stop.\n"); + + netif_carrier_off(netdev); + netif_tx_disable(netdev); + unic_dev_stop(netdev); + unic_disable_channels(unic_dev); + unic_clear_all_queue(netdev); + unic_reset_tx_queue(netdev); + + return 0; +} + +static void unic_fetch_stats_tx(struct rtnl_link_stats64 *stats, + struct unic_channel *channel) +{ + unsigned int start; + + do { + start = u64_stats_fetch_begin(&channel->sq->syncp); + + stats->tx_bytes += channel->sq->stats.bytes; + stats->tx_packets += channel->sq->stats.packets; + stats->tx_errors += channel->sq->stats.pad_err; + stats->tx_errors += channel->sq->stats.map_err; + stats->tx_errors += channel->sq->stats.over_max_sge_num; + stats->tx_errors += channel->sq->stats.csum_err; + stats->tx_errors += channel->sq->stats.vlan_err; + stats->tx_errors += channel->sq->stats.fd_cnt; + stats->tx_errors += channel->sq->stats.drop_cnt; + stats->tx_errors += channel->sq->stats.cfg5_drop_cnt; + + stats->tx_dropped += channel->sq->stats.pad_err; + stats->tx_dropped += channel->sq->stats.map_err; + stats->tx_dropped += channel->sq->stats.over_max_sge_num; + stats->tx_dropped += channel->sq->stats.csum_err; + stats->tx_dropped += channel->sq->stats.vlan_err; + stats->tx_dropped += channel->sq->stats.fd_cnt; + stats->tx_dropped += channel->sq->stats.drop_cnt; + stats->tx_dropped += channel->sq->stats.cfg5_drop_cnt; + } while (u64_stats_fetch_retry(&channel->sq->syncp, start)); +} + +static void unic_fetch_stats_rx(struct rtnl_link_stats64 *stats, + struct unic_channel *channel) +{ + unsigned int start; + + do { + start = u64_stats_fetch_begin(&channel->rq->syncp); + + stats->rx_bytes += channel->rq->stats.bytes; + stats->rx_packets += channel->rq->stats.packets; + stats->rx_crc_errors += channel->rq->stats.l2_err; + stats->rx_errors += channel->rq->stats.l3_l4_csum_err; + stats->rx_errors += channel->rq->stats.l2_err; + stats->rx_length_errors += channel->rq->stats.err_pkt_len_cnt; + + stats->rx_dropped += channel->rq->stats.alloc_skb_err; + stats->rx_dropped += channel->rq->stats.l2_err; + stats->rx_dropped += channel->rq->stats.err_pkt_len_cnt; + stats->rx_dropped += channel->rq->stats.trunc_cnt; + stats->rx_dropped += channel->rq->stats.doi_cnt; + stats->multicast += channel->rq->stats.multicast; + } while (u64_stats_fetch_retry(&channel->rq->syncp, start)); +} + +static void unic_get_stats64(struct net_device *netdev, + struct rtnl_link_stats64 *stats) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + u32 channel_num = unic_dev->channels.num; + struct rtnl_link_stats64 total_stats; + struct unic_channel *channel; + u32 i; + + if (test_bit(UNIC_STATE_DOWN, &unic_dev->state)) + return; + + mutex_lock(&unic_dev->channels.mutex); + if (!unic_dev->channels.c) { + mutex_unlock(&unic_dev->channels.mutex); + return; + } + + memset(&total_stats, 0, sizeof(total_stats)); + for (i = 0; i < channel_num; i++) { + channel = &unic_dev->channels.c[i]; + unic_fetch_stats_tx(&total_stats, channel); + unic_fetch_stats_rx(&total_stats, channel); + } + + mutex_unlock(&unic_dev->channels.mutex); + + stats->tx_bytes = total_stats.tx_bytes; + stats->tx_packets = total_stats.tx_packets; + stats->tx_errors = total_stats.tx_errors; + stats->tx_dropped = total_stats.tx_dropped; + stats->tx_fifo_errors = netdev->stats.tx_fifo_errors; + stats->tx_compressed = netdev->stats.tx_compressed; + stats->tx_aborted_errors = netdev->stats.tx_aborted_errors; + stats->tx_carrier_errors = netdev->stats.tx_carrier_errors; + stats->tx_heartbeat_errors = netdev->stats.tx_heartbeat_errors; + stats->tx_window_errors = netdev->stats.tx_window_errors; + stats->collisions = netdev->stats.collisions; + + stats->rx_bytes = total_stats.rx_bytes; + stats->rx_packets = total_stats.rx_packets; + stats->rx_errors = total_stats.rx_errors; + stats->rx_dropped = total_stats.rx_dropped; + stats->rx_length_errors = total_stats.rx_length_errors; + stats->rx_crc_errors = total_stats.rx_crc_errors; + stats->multicast = total_stats.multicast; + stats->rx_missed_errors = netdev->stats.rx_missed_errors; + stats->rx_over_errors = netdev->stats.rx_over_errors; + stats->rx_frame_errors = netdev->stats.rx_frame_errors; + stats->rx_fifo_errors = netdev->stats.rx_fifo_errors; + stats->rx_compressed = netdev->stats.rx_compressed; +} + +static int unic_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct unic_dev *unic_dev = (struct unic_dev *)netdev_priv(netdev); + int ret; + + if (netif_running(netdev)) { + unic_err(unic_dev, "failed to change MTU, due to network interface is up, please down it first and try again.\n"); + return -EBUSY; + } + + if (unic_resetting(netdev)) + return -EBUSY; + + if (netif_msg_ifdown(unic_dev)) + unic_info(unic_dev, "change mtu from %u to %d.\n", + netdev->mtu, new_mtu); + + ret = unic_set_mtu(unic_dev, new_mtu); + if (ret) { + unic_err(unic_dev, "failed to change MTU in hardware, ret = %d.\n", + ret); + return ret; + } + + netdev->mtu = (u32)new_mtu; + + return 0; +} + +static u8 unic_get_netdev_flags(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + u8 flags = 0; + + if (netdev->flags & IFF_PROMISC) { + if (unic_dev_ubl_supported(unic_dev)) + flags = UNIC_USER_UPE | UNIC_USER_MPE; + } else if (netdev->flags & IFF_ALLMULTI) { + flags = UNIC_USER_MPE; + } + + return flags; +} + +static void unic_set_rx_mode(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + struct unic_vport *vport = &unic_dev->vport; + u8 new_flags; + + new_flags = unic_get_netdev_flags(netdev); + unic_dev->netdev_flags = new_flags; + + set_bit(UNIC_VPORT_STATE_PROMISC_CHANGE, &vport->state); +} + +static void unic_tx_timeout(struct net_device *netdev, u32 queue_idx) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + + unic_dump_sq_stats(netdev, queue_idx); + + ubase_reset_event(unic_dev->comdev.adev, UBASE_UE_RESET); +} + +static u8 unic_get_skb_dscp(struct sk_buff *skb) +{ +#define UNIC_INVALID_DSCP 0xff +#define UNIC_DSCP_SHIFT 2 + + __be16 protocol = skb->protocol; + u8 dscp = UNIC_INVALID_DSCP; + + if (protocol == htons(ETH_P_8021Q)) + protocol = vlan_get_protocol(skb); + + if (protocol == htons(ETH_P_IP)) + dscp = ipv4_get_dsfield(ip_hdr(skb)) >> UNIC_DSCP_SHIFT; + else if (protocol == htons(ETH_P_IPV6)) + dscp = ipv6_get_dsfield(ipv6_hdr(skb)) >> UNIC_DSCP_SHIFT; + + return dscp; +} + +static u16 unic_select_queue(struct net_device *netdev, struct sk_buff *skb, + struct net_device *sb_dev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + struct unic_vl *vl = &unic_dev->channels.vl; + u8 dscp; + + if (!vl->dscp_app_cnt) + goto out; + + dscp = unic_get_skb_dscp(skb); + if (unlikely(dscp >= UBASE_MAX_DSCP)) + goto out; + + if (vl->dscp_prio[dscp] == UNIC_INVALID_PRIORITY) + skb->priority = 0; + else + skb->priority = vl->dscp_prio[dscp]; + +out: + return netdev_pick_tx(netdev, skb, sb_dev); +} + +static const struct net_device_ops unic_netdev_ops = { + .ndo_get_stats64 = unic_get_stats64, + .ndo_start_xmit = unic_start_xmit, + .ndo_tx_timeout = unic_tx_timeout, + .ndo_change_mtu = unic_change_mtu, + .ndo_open = unic_net_open, + .ndo_stop = unic_net_stop, + .ndo_set_rx_mode = unic_set_rx_mode, + .ndo_select_queue = unic_select_queue, +}; + +void unic_set_netdev_ops(struct net_device *netdev) +{ + netdev->netdev_ops = &unic_netdev_ops; +} + +static bool unic_port_dev_check(const struct net_device *dev) +{ + return dev->netdev_ops == &unic_netdev_ops; +} + +static int unic_ipaddr_event(struct notifier_block *nb, unsigned long event, + struct sockaddr *sa, struct net_device *ndev, + u16 ip_mask) +{ + struct unic_dev *unic_dev; + int ret; + + if (ndev->type != ARPHRD_UB) + return NOTIFY_DONE; + + if (!unic_port_dev_check(ndev)) + return NOTIFY_DONE; + + unic_dev = netdev_priv(ndev); + + switch (event) { + case NETDEV_UP: + ret = unic_add_ip_addr(unic_dev, sa, ip_mask); + if (ret) + return NOTIFY_BAD; + break; + case NETDEV_DOWN: + ret = unic_rm_ip_addr(unic_dev, sa, ip_mask); + if (ret) + return NOTIFY_BAD; + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int unic_inetaddr_event(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct in_ifaddr *ifa4 = (struct in_ifaddr *)ptr; + struct net_device *ndev = (struct net_device *)ifa4->ifa_dev->dev; + u16 ip4_mask = (u16)ifa4->ifa_prefixlen; + struct sockaddr_in in; + + in.sin_family = AF_INET; + in.sin_addr.s_addr = ifa4->ifa_address; + + return unic_ipaddr_event(nb, event, (struct sockaddr *)&in, ndev, + ip4_mask); +} + +static int unic_inet6addr_event(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct inet6_ifaddr *ifa6 = (struct inet6_ifaddr *)ptr; + struct net_device *ndev = (struct net_device *)ifa6->idev->dev; + u16 ip6_mask = (u16)ifa6->prefix_len; + struct sockaddr_in6 in6; + + in6.sin6_family = AF_INET6; + in6.sin6_addr = ifa6->addr; + + return unic_ipaddr_event(nb, event, (struct sockaddr *)&in6, ndev, + ip6_mask); +} + +static struct notifier_block unic_inetaddr_notifier = { + .notifier_call = unic_inetaddr_event +}; + +static struct notifier_block unic_inet6addr_notifier = { + .notifier_call = unic_inet6addr_event +}; + +int unic_register_ipaddr_notifier(void) +{ + int ret; + + ret = register_inetaddr_notifier(&unic_inetaddr_notifier); + if (ret) + return ret; + + ret = register_inet6addr_notifier(&unic_inet6addr_notifier); + if (ret) + unregister_inetaddr_notifier(&unic_inetaddr_notifier); + + return ret; +} + +void unic_unregister_ipaddr_notifier(void) +{ + unregister_inetaddr_notifier(&unic_inetaddr_notifier); + unregister_inet6addr_notifier(&unic_inet6addr_notifier); +} + +int unic_query_link_status(struct unic_dev *unic_dev, u8 *link_status) +{ + struct unic_link_status_cmd_resp resp = {0}; + struct ubase_cmd_buf out; + struct ubase_cmd_buf in; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_LINK_STATUS, true, 0, NULL); + + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_LINK_STATUS, false, + sizeof(resp), &resp); + + ret = ubase_cmd_send_inout(unic_dev->comdev.adev, &in, &out); + if (ret) { + unic_err(unic_dev, + "failed to get mac link status, ret = %d.\n", ret); + return ret; + } + + *link_status = (resp.status & UNIC_LINK_STATUS_UP_M) > 0 ? + UNIC_LINK_STATUS_UP : UNIC_LINK_STATUS_DOWN; + + return 0; +} diff --git a/drivers/net/ub/unic/unic_netdev.h b/drivers/net/ub/unic/unic_netdev.h new file mode 100644 index 0000000000000000000000000000000000000000..fd8fd16f1e922c614698e5838c7dac2749ecc8df --- /dev/null +++ b/drivers/net/ub/unic/unic_netdev.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_NETDEV_H__ +#define __UNIC_NETDEV_H__ + +#include + +#include "unic_dev.h" + +int unic_net_open(struct net_device *netdev); +int unic_net_open_no_link_change(struct net_device *netdev); +int unic_net_stop(struct net_device *netdev); +void unic_net_stop_no_link_change(struct net_device *netdev); +void unic_set_netdev_ops(struct net_device *netdev); +void unic_link_status_update(struct unic_dev *unic_dev); +int unic_register_ipaddr_notifier(void); +void unic_unregister_ipaddr_notifier(void); +void unic_link_status_change(struct net_device *netdev, bool linkup); +int unic_query_link_status(struct unic_dev *unic_dev, u8 *link_status); + +#endif diff --git a/drivers/net/ub/unic/unic_qos_hw.c b/drivers/net/ub/unic/unic_qos_hw.c new file mode 100644 index 0000000000000000000000000000000000000000..2f463be3d3242bdbd81ab81014352b30cb7d75fa --- /dev/null +++ b/drivers/net/ub/unic/unic_qos_hw.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include "unic_hw.h" +#include "unic_qos_hw.h" + +int unic_set_hw_vl_map(struct unic_dev *unic_dev, u8 *dscp_vl, u8 *prio_vl, + u8 map_type) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_config_vl_map_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + memcpy(req.dscp_vl, dscp_vl, sizeof(req.dscp_vl)); + memcpy(req.prio_vl, prio_vl, sizeof(req.prio_vl)); + req.map_type = map_type; + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_VL_MAP, false, + sizeof(req), &req); + ret = ubase_cmd_send_in(adev, &in); + if (ret) + dev_err(adev->dev.parent, "failed to set vl map. ret = %d.\n", ret); + + return ret; +} + +/* vl_maxrate: byte per second */ +int unic_config_vl_rate_limit(struct unic_dev *unic_dev, u64 *vl_maxrate, + u16 vl_bitmap) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_caps *caps = ubase_get_dev_caps(adev); + u32 max_speed = unic_dev->hw.mac.max_speed; + struct unic_config_vl_speed_cmd req = {0}; + struct ubase_cmd_buf in; + u32 vl_rate; + int i, ret; + + req.bus_ue_id = cpu_to_le16(USHRT_MAX); + req.vl_bitmap = cpu_to_le16(vl_bitmap); + for (i = 0; i < caps->vl_num; i++) { + vl_rate = vl_maxrate[i] / UNIC_MBYTE_PER_SEND; + vl_rate = vl_rate ? vl_rate : max_speed; + req.max_speed[caps->req_vl[i]] = cpu_to_le32(vl_rate); + req.max_speed[caps->resp_vl[i]] = cpu_to_le32(vl_rate); + } + + ubase_fill_inout_buf(&in, UBASE_OPC_VL_RATE_LIMIT_CONFIG, false, + sizeof(req), &req); + + ret = ubase_cmd_send_in(adev, &in); + if (ret) + dev_err(adev->dev.parent, + "failed to config vl rate limit, ret = %d.\n", ret); + + return ret; +} diff --git a/drivers/net/ub/unic/unic_qos_hw.h b/drivers/net/ub/unic/unic_qos_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..9f69474633493d200f75c6de7357f79b962afceb --- /dev/null +++ b/drivers/net/ub/unic/unic_qos_hw.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_QOS_HW_H__ +#define __UNIC_QOS_HW_H__ + +#include + +#include "unic_dev.h" + +int unic_set_hw_vl_map(struct unic_dev *unic_dev, u8 *dscp_vl, u8 *prio_vl, + u8 map_type); +int unic_config_vl_rate_limit(struct unic_dev *unic_dev, u64 *vl_maxrate, + u16 vl_bitmap); + +#endif diff --git a/drivers/net/ub/unic/unic_rack_ip.c b/drivers/net/ub/unic/unic_rack_ip.c new file mode 100644 index 0000000000000000000000000000000000000000..f2ff97304e78bc5e180b5daf83a779641431ed20 --- /dev/null +++ b/drivers/net/ub/unic/unic_rack_ip.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "unic_comm_addr.h" +#include "unic_trace.h" +#include "unic_rack_ip.h" + +static void unic_update_rack_addr_state(struct unic_vport *vport, + struct unic_comm_addr_node *addr_node, + enum UNIC_COMM_ADDR_STATE state, + const u8 *addr) +{ + struct auxiliary_device *adev = vport->back->comdev.adev; + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + char format_masked_ip_addr[UNIC_MASKED_FORMAT_IP_LEN]; + + /* update the state of address node by stack in rack server. + * if ip node exist in ip_list and receive the ack form stack, + * update_rack_addr_state and handle accidental deletion. + */ + switch (state) { + case UNIC_COMM_ADDR_TO_ADD: + if (addr_node->state == UNIC_COMM_ADDR_TO_ADD) + addr_node->state = UNIC_COMM_ADDR_ACTIVE; + break; + case UNIC_COMM_ADDR_TO_DEL: + if (addr_node->state == UNIC_COMM_ADDR_TO_DEL) { + list_del(&addr_node->node); + kfree(addr_node); + } else if (addr_node->state == UNIC_COMM_ADDR_ACTIVE) { + addr_node->state = UNIC_COMM_ADDR_TO_ADD; + unic_format_masked_ip_addr(format_masked_ip_addr, addr); + unic_info(unic_dev, + "deleted an existing ip %s by accident and need to add it.\n", + format_masked_ip_addr); + } + break; + default: + break; + } +} + +static int unic_update_stack_ip_addr(struct unic_vport *vport, + enum UNIC_COMM_ADDR_STATE state, + const u8 *addr, u16 ip_mask) +{ + spinlock_t *addr_list_lock = &vport->addr_tbl.ip_list_lock; + struct auxiliary_device *adev = vport->back->comdev.adev; + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + char format_masked_ip_addr[UNIC_MASKED_FORMAT_IP_LEN]; + struct list_head *list = &vport->addr_tbl.ip_list; + struct unic_comm_addr_node *addr_node, *tmp; + int ret = 0; + + clear_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state); + + spin_lock_bh(addr_list_lock); + + addr_node = unic_comm_find_addr_node(list, addr, ip_mask); + if (addr_node) { + unic_update_rack_addr_state(vport, addr_node, state, addr); + goto finish_update_state; + } + + if (state == UNIC_COMM_ADDR_TO_DEL) { + ret = -ENOENT; + goto finish_update_state; + } + + /* handle accidental addition, create addr node for deleting*/ + addr_node = kzalloc(sizeof(*addr_node), GFP_ATOMIC); + if (!addr_node) { + ret = -ENOMEM; + goto finish_update_state; + } + + addr_node->state = UNIC_COMM_ADDR_TO_DEL; + addr_node->node_mask = ip_mask; + memcpy(addr_node->unic_addr, addr, UNIC_ADDR_LEN); + list_add_tail(&addr_node->node, list); + unic_format_masked_ip_addr(format_masked_ip_addr, addr); + unic_info(unic_dev, + "added a new ip %s by accident and need to delete it.\n", + format_masked_ip_addr); + set_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state); + goto unlock_and_exit; + +finish_update_state: + list_for_each_entry(tmp, list, node) { + if (tmp->state != UNIC_COMM_ADDR_ACTIVE) { + set_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state); + break; + } + } +unlock_and_exit: + spin_unlock_bh(addr_list_lock); + + return ret; +} + +int unic_handle_stack_ip_feedback(struct unic_vport *vport, + enum UNIC_COMM_ADDR_STATE state, + struct sockaddr *addr, u16 ip_mask) +{ + struct auxiliary_device *adev = vport->back->comdev.adev; + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + char format_masked_ip_addr[UNIC_MASKED_FORMAT_IP_LEN]; + struct in6_addr ip_addr; + int ret; + + ret = unic_convert_ip_addr(addr, &ip_addr); + if (ret) + unic_info(unic_dev, + "invalid IP protocol type, ret = %d.\n", ret); + + ret = unic_update_stack_ip_addr(vport, state, + (const unsigned char *)&ip_addr, + ip_mask); + if (ret) { + unic_format_masked_ip_addr(format_masked_ip_addr, (u8 *)&ip_addr); + unic_err(unic_dev, "failed to %s IP %s from ip list, ret = %d.\n", + state == UNIC_COMM_ADDR_TO_ADD ? "add" : "delete", + format_masked_ip_addr, ret); + } + + return ret; +} + +static bool unic_is_ipv4(struct unic_stack_ip_info *ip_info) +{ + if (ip_info->ip_addr[0] == 0x0 && ip_info->ip_addr[1] == 0x0 && + ip_info->ip_addr[2] == UNIC_IPV4_PREFIX) + return true; + else + return false; +} + +static int unic_fill_rtattr_pack(struct nlmsghdr *nh, size_t req_sz, + unsigned short rta_type, const void *payload, + size_t size) +{ + size_t nl_size = RTA_ALIGN(nh->nlmsg_len) + RTA_LENGTH(size); + struct rtattr *attr = (struct rtattr *)((char *)(nh) + + RTA_ALIGN(nh->nlmsg_len)); + + if (req_sz < nl_size) + return -EINVAL; + + nh->nlmsg_len = nl_size; + attr->rta_len = RTA_LENGTH(size); + attr->rta_type = rta_type; + memcpy(RTA_DATA(attr), payload, size); + + return 0; +} + +static int unic_fill_netlink_ip_req(struct unic_netlink_ip_req *req, + struct sk_buff *skb, struct net_device *dev, + struct unic_stack_ip_info *ip_info, + sa_family_t sa_family) +{ + int ret; + + NETLINK_CB(skb).portid = 0; + NETLINK_CB(skb).dst_group = 0; + NETLINK_CB(skb).flags = NETLINK_SKB_DST; + memset(req, 0, sizeof(*req)); + + if (ip_info->ip_cmd == UNIC_COMM_ADDR_TO_ADD) { + req->nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; + req->nh.nlmsg_type = RTM_NEWADDR; + } else { + req->nh.nlmsg_flags = NLM_F_REQUEST; + req->nh.nlmsg_type = RTM_DELADDR; + } + + req->nh.nlmsg_len = NLMSG_LENGTH(sizeof(req->info)); + req->info.ifa_family = sa_family; + req->info.ifa_flags = IFA_F_PERMANENT; + req->info.ifa_scope = RT_SCOPE_UNIVERSE; + req->info.ifa_prefixlen = (u8)ip_info->ip_mask; + req->info.ifa_index = dev->ifindex; + + if (sa_family == AF_INET) { + ret = unic_fill_rtattr_pack(&req->nh, sizeof(*req), IFA_LOCAL, + &ip_info->ip_addr[UNIC_IP_FOURTH_PART], + sizeof(ip_info->ip_addr[UNIC_IP_FOURTH_PART])); + if (ret) + return ret; + + ret = unic_fill_rtattr_pack(&req->nh, sizeof(*req), IFA_ADDRESS, + &ip_info->ip_addr[UNIC_IP_FOURTH_PART], + sizeof(ip_info->ip_addr[UNIC_IP_FOURTH_PART])); + } else { + ret = unic_fill_rtattr_pack(&req->nh, sizeof(*req), IFA_LOCAL, + ip_info->ip_addr, + sizeof(ip_info->ip_addr)); + if (ret) + return ret; + + ret = unic_fill_rtattr_pack(&req->nh, sizeof(*req), IFA_ADDRESS, + ip_info->ip_addr, + sizeof(ip_info->ip_addr)); + } + + return ret; +} + +static int unic_set_stack_ip(struct net_device *dev, + struct unic_stack_ip_info *ip_info) +{ + struct unic_dev *unic_dev = netdev_priv(dev); + struct net *tmpdev = dev_net(dev); + struct unic_netlink_ip_req *req; + struct sock *sk = tmpdev->rtnl; + sa_family_t sa_family; + struct sk_buff *skb; + int ret; + + skb = alloc_skb(NLMSG_ALIGN(sizeof(*req)), GFP_KERNEL); + if (!skb) { + unic_info(unic_dev, + "failed to alloc skb, unic stop setting stack ip.\n"); + return -ENOMEM; + } + + req = skb_put(skb, sizeof(*req)); + sa_family = unic_is_ipv4(ip_info) ? AF_INET : AF_INET6; + ret = unic_fill_netlink_ip_req(req, skb, dev, ip_info, sa_family); + if (ret) { + unic_err(unic_dev, + "failed to fill netlink ip req, ret = %d.\n", ret); + goto failed_packet_seq; + } + + trace_unic_ip_req_skb(dev, skb, &req->nh, &req->info, req->attrbuf); + netlink_unicast(sk, skb, 0, 0); + + return 0; + +failed_packet_seq: + kfree_skb(skb); + + return ret; +} + +static int unic_sync_stack_ip(struct unic_vport *vport, + struct unic_comm_addr_node *ip_node, u16 ip_cmd) +{ + struct net_device *netdev = vport->back->comdev.netdev; + struct unic_dev *unic_dev = netdev_priv(netdev); + struct unic_stack_ip_info ip_info; + int ret; + + ip_info.ip_cmd = ip_cmd; + ip_info.ip_mask = ip_node->node_mask; + memcpy(&ip_info.ip_addr, &ip_node->ip_addr, sizeof(ip_info.ip_addr)); + + ret = unic_set_stack_ip(netdev, &ip_info); + if (ret) + unic_err(unic_dev, + "failed to set ip to stack, ret = %d.\n", ret); + + return ret; +} + +static void unic_sync_rack_ip_list(struct unic_vport *vport, + struct list_head *list, + enum unic_ctrlq_ip_event state) +{ + struct unic_comm_addr_node *ip_node, *tmp; + int ret; + + list_for_each_entry_safe(ip_node, tmp, list, node) { + ret = unic_sync_stack_ip(vport, ip_node, state); + if (ret) + set_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state); + + list_del(&ip_node->node); + kfree(ip_node); + } +} + +static void unic_rack_sync_addr_table(struct unic_vport *vport, + struct list_head *list, + spinlock_t *addr_list_lock) +{ + struct auxiliary_device *adev = vport->back->comdev.adev; + struct unic_comm_addr_node *addr_node, *tmp, *new_node; + struct list_head tmp_add_list, tmp_del_list; + + INIT_LIST_HEAD(&tmp_add_list); + INIT_LIST_HEAD(&tmp_del_list); + + /* move the addr to the tmp_add_list and tmp_del_list, then + * we can add/delete these addr outside the spin lock + */ + spin_lock_bh(addr_list_lock); + + list_for_each_entry_safe(addr_node, tmp, list, node) { + if (addr_node->state == UNIC_COMM_ADDR_ACTIVE) + continue; + + new_node = kzalloc(sizeof(*new_node), GFP_ATOMIC); + if (!new_node) { + dev_err_ratelimited(&adev->dev, + "failed to alloc memory for new node.\n"); + goto stop_traverse; + } + memcpy(new_node->unic_addr, addr_node->unic_addr, UNIC_ADDR_LEN); + new_node->state = addr_node->state; + new_node->node_mask = addr_node->node_mask; + + if (new_node->state == UNIC_COMM_ADDR_TO_DEL) + list_add_tail(&new_node->node, &tmp_del_list); + else + list_add_tail(&new_node->node, &tmp_add_list); + } + +stop_traverse: + spin_unlock_bh(addr_list_lock); + + unic_sync_rack_ip_list(vport, &tmp_del_list, UNIC_CTRLQ_DEL_IP); + unic_sync_rack_ip_list(vport, &tmp_add_list, UNIC_CTRLQ_ADD_IP); +} + +void unic_sync_rack_ip_table(struct unic_dev *unic_dev) +{ + struct unic_vport *vport = &unic_dev->vport; + + if (!test_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state)) + return; + + unic_rack_sync_addr_table(vport, &vport->addr_tbl.ip_list, + &vport->addr_tbl.ip_list_lock); +} + +static void unic_build_stack_ip_info(struct unic_ctrlq_ip_notify_req *req, + struct unic_stack_ip_info *st_ip) +{ +#define le32_to_be32(x) cpu_to_be32(le32_to_cpu(x)) + + st_ip->ip_cmd = le16_to_cpu(req->ip_cmd); + st_ip->ip_mask = le16_to_cpu(req->ip_mask); + st_ip->ip_addr[0] = le32_to_be32(req->ip_addr[3]); + st_ip->ip_addr[1] = le32_to_be32(req->ip_addr[2]); + st_ip->ip_addr[2] = le32_to_be32(req->ip_addr[1]); + st_ip->ip_addr[3] = le32_to_be32(req->ip_addr[0]); +} + +static int unic_update_rack_addr_list(struct list_head *list, + spinlock_t *addr_list_lock, + enum UNIC_COMM_ADDR_STATE state, + const u8 *addr, u16 ip_mask) +{ + struct unic_comm_addr_node *addr_node; + + spin_lock_bh(addr_list_lock); + + /* if the addr is already in the addr list, no need to add a new + * one into it, just check the addr state, convert it to a new + * state, or just remove it, or do nothing. + */ + addr_node = unic_comm_find_addr_node(list, addr, ip_mask); + if (addr_node) { + unic_comm_update_addr_state(addr_node, state); + spin_unlock_bh(addr_list_lock); + return 0; + } + + /* if this addr is never added, unnecessary to delete */ + if (state == UNIC_COMM_ADDR_TO_DEL) { + spin_unlock_bh(addr_list_lock); + return -ENOENT; + } + + addr_node = kzalloc(sizeof(*addr_node), GFP_ATOMIC); + if (!addr_node) { + spin_unlock_bh(addr_list_lock); + return -ENOMEM; + } + + addr_node->state = state; + addr_node->node_mask = ip_mask; + memcpy(addr_node->unic_addr, addr, UNIC_ADDR_LEN); + list_add_tail(&addr_node->node, list); + + spin_unlock_bh(addr_list_lock); + + return 0; +} + +static void unic_send_notify_ip_resp(struct auxiliary_device *adev, u16 seq, + u8 result) +{ + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + struct unic_ctrlq_ip_notify_resp resp = {0}; + struct ubase_ctrlq_msg msg = {0}; + int ret; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_IP_ACL; + msg.opcode = UBASE_CTRLQ_OPC_NOTIFY_IP; + msg.in_size = sizeof(resp); + msg.in = &resp; + msg.is_resp = 1; + msg.resp_seq = seq; + msg.resp_ret = result; + + ret = ubase_ctrlq_send_msg(adev, &msg); + if (ret) + unic_err(unic_dev, + "failed to notify ip resp by ctrlq, ret = %d.\n", ret); +} + +static int unic_update_tmp_ip_list(struct unic_dev *unic_dev, + struct list_head *list, + struct unic_stack_ip_info *st_ip) +{ + struct unic_comm_addr_node *addr_node; + enum UNIC_COMM_ADDR_STATE state; + + if (st_ip->ip_cmd == UNIC_CTRLQ_ADD_IP) { + state = UNIC_COMM_ADDR_TO_ADD; + } else if (st_ip->ip_cmd == UNIC_CTRLQ_DEL_IP) { + state = UNIC_COMM_ADDR_TO_DEL; + } else { + unic_err(unic_dev, "invalid ip cmd by ctrlq, ip cmd = %u.\n", + st_ip->ip_cmd); + return -EINVAL; + } + + addr_node = unic_comm_find_addr_node(list, (u8 *)&st_ip->ip_addr, + st_ip->ip_mask); + if (addr_node) { + addr_node->state = state; + return 0; + } + + addr_node = kzalloc(sizeof(*addr_node), GFP_ATOMIC); + if (!addr_node) + return -ENOMEM; + + addr_node->state = state; + addr_node->node_mask = st_ip->ip_mask; + memcpy(addr_node->unic_addr, (u8 *)&st_ip->ip_addr, UNIC_ADDR_LEN); + list_add_tail(&addr_node->node, list); + + return 0; +} + +int unic_handle_notify_ip_event(struct auxiliary_device *adev, u8 service_ver, + void *data, u16 len, u16 seq) +{ + struct unic_dev *priv = (struct unic_dev *)dev_get_drvdata(&adev->dev); + char format_ip[UNIC_MASKED_FORMAT_IP_LEN]; + struct unic_vport *vport = &priv->vport; + struct unic_ctrlq_ip_notify_req *req; + struct unic_stack_ip_info st_ip; + int ret; + + if (len < sizeof(*req)) { + unic_err(priv, "failed to verify ip info size, len = %u.\n", len); + return -EINVAL; + } + + req = (struct unic_ctrlq_ip_notify_req *)data; + + unic_build_stack_ip_info(req, &st_ip); + + spin_lock_bh(&vport->addr_tbl.tmp_ip_lock); + if (test_bit(UNIC_VPORT_STATE_IP_QUERYING, &vport->state)) { + ret = unic_update_tmp_ip_list(priv, &vport->addr_tbl.tmp_ip_list, + &st_ip); + goto out; + } + + if (st_ip.ip_cmd == UNIC_CTRLQ_ADD_IP) { + ret = unic_update_rack_addr_list(&vport->addr_tbl.ip_list, + &vport->addr_tbl.ip_list_lock, + UNIC_COMM_ADDR_TO_ADD, + (u8 *)&st_ip.ip_addr, + st_ip.ip_mask); + } else if (st_ip.ip_cmd == UNIC_CTRLQ_DEL_IP) { + ret = unic_update_rack_addr_list(&vport->addr_tbl.ip_list, + &vport->addr_tbl.ip_list_lock, + UNIC_COMM_ADDR_TO_DEL, + (u8 *)&st_ip.ip_addr, + st_ip.ip_mask); + } else { + ret = -EINVAL; + unic_err(priv, "invalid ip cmd by ctrlq, cmd = %u.\n", st_ip.ip_cmd); + goto out; + } + + if (ret == -ENOENT) { + unic_format_masked_ip_addr(format_ip, (u8 *)&st_ip.ip_addr); + unic_err(priv, "failed to delete IP %s from ip list.\n", + format_ip); + ret = 0; + goto out; + } + + if (!ret) + set_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state); + +out: + spin_unlock_bh(&vport->addr_tbl.tmp_ip_lock); + unic_send_notify_ip_resp(adev, seq, (u8)(-ret)); + + return ret; +} + +static int unic_update_ctrlq_ip_list(struct unic_ip_info *ip_info, + struct unic_vport *vport, + struct list_head *list) +{ +#define le32_to_be32(x) cpu_to_be32(le32_to_cpu(x)) + + struct unic_comm_addr_node *ip_node, *new_node, *tmp_node; + struct unic_stack_ip_info st_ip; + + st_ip.ip_mask = le16_to_cpu(ip_info->ip_mask); + st_ip.ip_addr[0] = le32_to_be32(ip_info->ip_addr[3]); + st_ip.ip_addr[1] = le32_to_be32(ip_info->ip_addr[2]); + st_ip.ip_addr[2] = le32_to_be32(ip_info->ip_addr[1]); + st_ip.ip_addr[3] = le32_to_be32(ip_info->ip_addr[0]); + + ip_node = kzalloc(sizeof(*ip_node), GFP_KERNEL); + if (!ip_node) + return -ENOMEM; + + ip_node->state = UNIC_COMM_ADDR_TO_ADD; + ip_node->node_mask = st_ip.ip_mask; + memcpy(ip_node->unic_addr, &st_ip.ip_addr, UNIC_ADDR_LEN); + + new_node = unic_comm_find_addr_node(&vport->addr_tbl.ip_list, + ip_node->unic_addr, + ip_node->node_mask); + if (new_node) { + if (new_node->state != UNIC_COMM_ADDR_TO_ADD) + new_node->state = UNIC_COMM_ADDR_ACTIVE; + + kfree(ip_node); + } else { + list_add_tail(&ip_node->node, &vport->addr_tbl.ip_list); + } + + tmp_node = kzalloc(sizeof(*tmp_node), GFP_KERNEL); + if (!tmp_node) + return -ENOMEM; + + tmp_node->state = UNIC_COMM_ADDR_TO_ADD; + tmp_node->node_mask = st_ip.ip_mask; + memcpy(tmp_node->unic_addr, &st_ip.ip_addr, UNIC_ADDR_LEN); + list_add_tail(&tmp_node->node, list); + + return 0; +} + +static int unic_ctrlq_query_ip(struct auxiliary_device *adev, u16 *ip_index, + u8 *get_count, struct unic_vport *vport, + struct list_head *list) +{ + struct unic_dev *unic_dev = dev_get_drvdata(&adev->dev); + struct unic_ctrlq_query_ip_resp resp = {0}; + struct unic_ctrlq_query_ip_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + int ret; + u8 i; + + req.ip_index = cpu_to_le16(*ip_index); + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_IP_ACL; + msg.opcode = UBASE_CTRLQ_OPC_QUERY_IP; + msg.need_resp = 1; + msg.is_resp = 0; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + + ret = ubase_ctrlq_send_msg(adev, &msg); + if (ret) { + unic_err(unic_dev, + "failed to query ip by ctrlq, ret = %d.\n", ret); + return ret; + } + + spin_lock_bh(&vport->addr_tbl.ip_list_lock); + + for (i = 0; i < resp.get_count; i++) { + ret = unic_update_ctrlq_ip_list(&resp.ip_info[i], vport, list); + if (ret) + goto out; + } + + *get_count = resp.get_count; + *ip_index = le16_to_cpu(resp.ip_index); + +out: + spin_unlock_bh(&vport->addr_tbl.ip_list_lock); + + return ret; +} + +static void unic_update_rack_ip_list(struct unic_vport *vport, + struct list_head *list) +{ + struct unic_comm_addr_node *ip_node, *tmp, *new_node; + + spin_lock_bh(&vport->addr_tbl.ip_list_lock); + + list_for_each_entry_safe(ip_node, tmp, &vport->addr_tbl.ip_list, node) { + new_node = unic_comm_find_addr_node(list, ip_node->unic_addr, + ip_node->node_mask); + if (!new_node) + ip_node->state = UNIC_COMM_ADDR_TO_DEL; + } + + list_for_each_entry_safe(ip_node, tmp, &vport->addr_tbl.tmp_ip_list, node) { + new_node = unic_comm_find_addr_node(&vport->addr_tbl.ip_list, + ip_node->unic_addr, + ip_node->node_mask); + if (new_node) { + unic_comm_update_addr_state(new_node, ip_node->state); + } else { + if (ip_node->state == UNIC_COMM_ADDR_TO_ADD) + list_move_tail(&ip_node->node, + &vport->addr_tbl.ip_list); + } + } + + list_for_each_entry_safe(ip_node, tmp, &vport->addr_tbl.ip_list, node) { + if (ip_node->state != UNIC_COMM_ADDR_ACTIVE) { + set_bit(UNIC_VPORT_STATE_IP_TBL_CHANGE, &vport->state); + break; + } + } + + spin_unlock_bh(&vport->addr_tbl.ip_list_lock); +} + +void unic_query_rack_ip(struct auxiliary_device *adev) +{ +#define UNIC_LOOP_COUNT(total_size, size) ((total_size) / (size) + 1) + + struct unic_dev *priv = (struct unic_dev *)dev_get_drvdata(&adev->dev); + struct unic_comm_addr_node *ip_node, *tmp; + struct unic_vport *vport = &priv->vport; + struct list_head tmp_list; + u16 ip_index = 0, cnt = 0; + u8 get_count = 0; + int ret; + + set_bit(UNIC_VPORT_STATE_IP_QUERYING, &priv->vport.state); + INIT_LIST_HEAD(&tmp_list); + + while (ip_index < UNIC_IP_TABLE_SIZE && + cnt < UNIC_LOOP_COUNT(UNIC_IP_TABLE_SIZE, UNIC_CTRLQ_IP_REQ_SIZE)) { + ret = unic_ctrlq_query_ip(adev, &ip_index, &get_count, vport, + &tmp_list); + if (ret) { + unic_err(priv, + "failed to query ip info by ctrlq, ret = %d.\n", + ret); + break; + } + + if (get_count != UNIC_CTRLQ_IP_REQ_SIZE) + break; + + ip_index++; + cnt++; + } + + spin_lock_bh(&vport->addr_tbl.tmp_ip_lock); + + unic_update_rack_ip_list(vport, &tmp_list); + clear_bit(UNIC_VPORT_STATE_IP_QUERYING, &vport->state); + + list_for_each_entry_safe(ip_node, tmp, &vport->addr_tbl.tmp_ip_list, node) { + list_del(&ip_node->node); + kfree(ip_node); + } + + spin_unlock_bh(&vport->addr_tbl.tmp_ip_lock); + + list_for_each_entry_safe(ip_node, tmp, &tmp_list, node) { + list_del(&ip_node->node); + kfree(ip_node); + } +} + +void unic_uninit_rack_ip_table(struct unic_dev *unic_dev) +{ + struct unic_vport *vport = &unic_dev->vport; + struct list_head *list = &vport->addr_tbl.ip_list; + struct unic_comm_addr_node *ip_node, *tmp; + + spin_lock_bh(&vport->addr_tbl.ip_list_lock); + + list_for_each_entry_safe(ip_node, tmp, list, node) { + list_del(&ip_node->node); + kfree(ip_node); + } + + spin_unlock_bh(&vport->addr_tbl.ip_list_lock); +} + +int unic_add_ip_addr(struct unic_dev *unic_dev, struct sockaddr *addr, + u16 ip_mask) +{ + return unic_handle_stack_ip_feedback(&unic_dev->vport, + UNIC_COMM_ADDR_TO_ADD, + addr, ip_mask); +} + +int unic_rm_ip_addr(struct unic_dev *unic_dev, struct sockaddr *addr, + u16 ip_mask) +{ + return unic_handle_stack_ip_feedback(&unic_dev->vport, + UNIC_COMM_ADDR_TO_DEL, + addr, ip_mask); +} diff --git a/drivers/net/ub/unic/unic_rack_ip.h b/drivers/net/ub/unic/unic_rack_ip.h new file mode 100644 index 0000000000000000000000000000000000000000..48f62eb0fb708058a43afa2f3b475d7088389f56 --- /dev/null +++ b/drivers/net/ub/unic/unic_rack_ip.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_RACK_IP_H__ +#define __UNIC_RACK_IP_H__ + +#include "unic_dev.h" +#include "unic_comm_addr.h" + +#define UNIC_RTATTR_PACK_LENGTH 256 +#define UNIC_IP_TABLE_SIZE 128 +#define UNIC_CTRLQ_IP_REQ_SIZE 4 +#define UNIC_IP_FOURTH_PART 3 +#define UNIC_MASKED_FORMAT_IP_LEN 40 + +enum unic_ctrlq_ip_event { + UNIC_CTRLQ_ADD_IP, + UNIC_CTRLQ_DEL_IP, +}; + +struct unic_netlink_ip_req { + struct nlmsghdr nh; + struct ifaddrmsg info; + char attrbuf[UNIC_RTATTR_PACK_LENGTH]; +}; + +struct unic_ctrlq_query_ip_req { + __le16 ip_index; + u8 resv[18]; +}; + +struct unic_ip_info { + __le32 ip_addr[UNIC_CTRLQ_IP_REQ_SIZE]; + __le16 ip_mask; + u8 resv[2]; +}; + +struct unic_ctrlq_query_ip_resp { + __le16 ip_index; + u8 get_count; + u8 resv; + struct unic_ip_info ip_info[UNIC_CTRLQ_IP_REQ_SIZE]; +}; + +struct unic_ctrlq_ip_notify_req { + __le16 ip_cmd; + __le16 ip_mask; + __le32 ip_addr[UNIC_CTRLQ_IP_REQ_SIZE]; +}; + +struct unic_ctrlq_ip_notify_resp { + u8 resv[20]; +}; + +struct unic_stack_ip_info { + u16 ip_cmd; + u16 ip_mask; + __be32 ip_addr[UNIC_CTRLQ_IP_REQ_SIZE]; +}; + +static inline void unic_format_masked_ip_addr(char *format_masked_ip_addr, + const u8 *ip_addr) +{ +#define IP_START_BYTE 12 + + snprintf(format_masked_ip_addr, UNIC_MASKED_FORMAT_IP_LEN, + "****:****:****:****:****:****:%02x%02x:%02x%02x", + ip_addr[IP_START_BYTE], ip_addr[IP_START_BYTE + 1], + ip_addr[IP_START_BYTE + 2], ip_addr[IP_START_BYTE + 3]); +} + +void unic_sync_rack_ip_table(struct unic_dev *unic_dev); +int unic_handle_notify_ip_event(struct auxiliary_device *adev, u8 service_ver, + void *data, u16 len, u16 seq); +void unic_query_rack_ip(struct auxiliary_device *adev); +void unic_uninit_rack_ip_table(struct unic_dev *unic_dev); +int unic_add_ip_addr(struct unic_dev *unic_dev, struct sockaddr *addr, + u16 ip_mask); +int unic_rm_ip_addr(struct unic_dev *unic_dev, struct sockaddr *addr, + u16 ip_mask); + +#endif diff --git a/drivers/net/ub/unic/unic_rx.c b/drivers/net/ub/unic/unic_rx.c new file mode 100644 index 0000000000000000000000000000000000000000..34095fbac40d54ae8b85be2de8ff1d46d1887233 --- /dev/null +++ b/drivers/net/ub/unic/unic_rx.c @@ -0,0 +1,1220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include +#include +#ifdef CONFIG_UB_UNIC_UBL +#include +#endif +#include + +#include "unic_dev.h" +#include "unic_trace.h" +#include "unic_rx.h" + +#define UNIC_RX_HEAD_SIZE 256 +#define UNIC_RQ_CI_REVERSE (USHRT_MAX + 1) + +#define UNIC_RX_PTYPE_ENTRY(ptype, csum_level, ip_summed, l3type, hash_type) \ + { ptype, csum_level, CHECKSUM_##ip_summed, UNIC_L3_TYPE_##l3type, \ + 1, hash_type, 0 } + +#define UNIC_RX_PTYPE_UNUSED_ENTRY(ptype) \ + { ptype, 0, CHECKSUM_NONE, UNIC_L3_TYPE_RESV, \ + 0, PKT_HASH_TYPE_NONE, 0 } + +static const struct unic_rx_ptype unic_rx_ptype_tbl[] = { + UNIC_RX_PTYPE_UNUSED_ENTRY(0), + UNIC_RX_PTYPE_ENTRY(1, 0, NONE, ARP, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(2, 0, NONE, RARP, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(3, 0, NONE, LLDP, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(4, 0, NONE, STP, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(5, 0, NONE, MAC_PAUSE, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(6, 0, NONE, PFC_PAUSE, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(7, 0, NONE, CNM, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(8, 0, NONE, PTP, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_UNUSED_ENTRY(9), + UNIC_RX_PTYPE_UNUSED_ENTRY(10), + UNIC_RX_PTYPE_UNUSED_ENTRY(11), + UNIC_RX_PTYPE_UNUSED_ENTRY(12), + UNIC_RX_PTYPE_UNUSED_ENTRY(13), + UNIC_RX_PTYPE_UNUSED_ENTRY(14), + UNIC_RX_PTYPE_UNUSED_ENTRY(15), + UNIC_RX_PTYPE_UNUSED_ENTRY(16), + UNIC_RX_PTYPE_ENTRY(17, 0, NONE, IPV4, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(18, 0, NONE, IPV4, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(19, 0, UNNECESSARY, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(20, 0, UNNECESSARY, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(21, 0, NONE, IPV4, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(22, 0, NONE, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(23, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(24, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(25, 0, NONE, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_UNUSED_ENTRY(26), + UNIC_RX_PTYPE_UNUSED_ENTRY(27), + UNIC_RX_PTYPE_UNUSED_ENTRY(28), + UNIC_RX_PTYPE_ENTRY(29, 0, NONE, IPV4, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(30, 0, NONE, IPV4, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(31, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(32, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(33, 0, UNNECESSARY, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(34, 0, UNNECESSARY, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(35, 0, NONE, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(36, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(37, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_UNUSED_ENTRY(38), + UNIC_RX_PTYPE_ENTRY(39, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(40, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(41, 0, UNNECESSARY, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(42, 0, UNNECESSARY, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(43, 0, NONE, IPV4, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(44, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(45, 0, NONE, IPV4, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_UNUSED_ENTRY(46), + UNIC_RX_PTYPE_UNUSED_ENTRY(47), + UNIC_RX_PTYPE_UNUSED_ENTRY(48), + UNIC_RX_PTYPE_UNUSED_ENTRY(49), + UNIC_RX_PTYPE_UNUSED_ENTRY(50), + UNIC_RX_PTYPE_UNUSED_ENTRY(51), + UNIC_RX_PTYPE_UNUSED_ENTRY(52), + UNIC_RX_PTYPE_UNUSED_ENTRY(53), + UNIC_RX_PTYPE_UNUSED_ENTRY(54), + UNIC_RX_PTYPE_UNUSED_ENTRY(55), + UNIC_RX_PTYPE_UNUSED_ENTRY(56), + UNIC_RX_PTYPE_UNUSED_ENTRY(57), + UNIC_RX_PTYPE_UNUSED_ENTRY(58), + UNIC_RX_PTYPE_UNUSED_ENTRY(59), + UNIC_RX_PTYPE_UNUSED_ENTRY(60), + UNIC_RX_PTYPE_UNUSED_ENTRY(61), + UNIC_RX_PTYPE_UNUSED_ENTRY(62), + UNIC_RX_PTYPE_UNUSED_ENTRY(63), + UNIC_RX_PTYPE_UNUSED_ENTRY(64), + UNIC_RX_PTYPE_UNUSED_ENTRY(65), + UNIC_RX_PTYPE_UNUSED_ENTRY(66), + UNIC_RX_PTYPE_UNUSED_ENTRY(67), + UNIC_RX_PTYPE_UNUSED_ENTRY(68), + UNIC_RX_PTYPE_UNUSED_ENTRY(69), + UNIC_RX_PTYPE_UNUSED_ENTRY(70), + UNIC_RX_PTYPE_UNUSED_ENTRY(71), + UNIC_RX_PTYPE_UNUSED_ENTRY(72), + UNIC_RX_PTYPE_UNUSED_ENTRY(73), + UNIC_RX_PTYPE_UNUSED_ENTRY(74), + UNIC_RX_PTYPE_UNUSED_ENTRY(75), + UNIC_RX_PTYPE_UNUSED_ENTRY(76), + UNIC_RX_PTYPE_UNUSED_ENTRY(77), + UNIC_RX_PTYPE_UNUSED_ENTRY(78), + UNIC_RX_PTYPE_UNUSED_ENTRY(79), + UNIC_RX_PTYPE_UNUSED_ENTRY(80), + UNIC_RX_PTYPE_UNUSED_ENTRY(81), + UNIC_RX_PTYPE_UNUSED_ENTRY(82), + UNIC_RX_PTYPE_UNUSED_ENTRY(83), + UNIC_RX_PTYPE_UNUSED_ENTRY(84), + UNIC_RX_PTYPE_UNUSED_ENTRY(85), + UNIC_RX_PTYPE_UNUSED_ENTRY(86), + UNIC_RX_PTYPE_UNUSED_ENTRY(87), + UNIC_RX_PTYPE_UNUSED_ENTRY(88), + UNIC_RX_PTYPE_UNUSED_ENTRY(89), + UNIC_RX_PTYPE_UNUSED_ENTRY(90), + UNIC_RX_PTYPE_UNUSED_ENTRY(91), + UNIC_RX_PTYPE_UNUSED_ENTRY(92), + UNIC_RX_PTYPE_UNUSED_ENTRY(93), + UNIC_RX_PTYPE_UNUSED_ENTRY(94), + UNIC_RX_PTYPE_UNUSED_ENTRY(95), + UNIC_RX_PTYPE_UNUSED_ENTRY(96), + UNIC_RX_PTYPE_UNUSED_ENTRY(97), + UNIC_RX_PTYPE_UNUSED_ENTRY(98), + UNIC_RX_PTYPE_UNUSED_ENTRY(99), + UNIC_RX_PTYPE_UNUSED_ENTRY(100), + UNIC_RX_PTYPE_UNUSED_ENTRY(101), + UNIC_RX_PTYPE_UNUSED_ENTRY(102), + UNIC_RX_PTYPE_UNUSED_ENTRY(103), + UNIC_RX_PTYPE_UNUSED_ENTRY(104), + UNIC_RX_PTYPE_UNUSED_ENTRY(105), + UNIC_RX_PTYPE_UNUSED_ENTRY(106), + UNIC_RX_PTYPE_UNUSED_ENTRY(107), + UNIC_RX_PTYPE_UNUSED_ENTRY(108), + UNIC_RX_PTYPE_UNUSED_ENTRY(109), + UNIC_RX_PTYPE_UNUSED_ENTRY(110), + UNIC_RX_PTYPE_ENTRY(111, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(112, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(113, 0, UNNECESSARY, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(114, 0, UNNECESSARY, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(115, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(116, 0, NONE, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(117, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(118, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(119, 0, NONE, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_UNUSED_ENTRY(120), + UNIC_RX_PTYPE_UNUSED_ENTRY(121), + UNIC_RX_PTYPE_UNUSED_ENTRY(122), + UNIC_RX_PTYPE_ENTRY(123, 0, NONE, IPV6, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(124, 0, NONE, IPV6, PKT_HASH_TYPE_NONE), + UNIC_RX_PTYPE_ENTRY(125, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(126, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(127, 0, UNNECESSARY, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(128, 0, UNNECESSARY, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(129, 0, NONE, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(130, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(131, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_UNUSED_ENTRY(132), + UNIC_RX_PTYPE_ENTRY(133, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(134, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(135, 0, UNNECESSARY, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(136, 0, UNNECESSARY, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(137, 0, NONE, IPV6, PKT_HASH_TYPE_L4), + UNIC_RX_PTYPE_ENTRY(138, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_ENTRY(139, 0, NONE, IPV6, PKT_HASH_TYPE_L3), + UNIC_RX_PTYPE_UNUSED_ENTRY(140), + UNIC_RX_PTYPE_UNUSED_ENTRY(141), + UNIC_RX_PTYPE_UNUSED_ENTRY(142), + UNIC_RX_PTYPE_UNUSED_ENTRY(143), + UNIC_RX_PTYPE_UNUSED_ENTRY(144), + UNIC_RX_PTYPE_UNUSED_ENTRY(145), + UNIC_RX_PTYPE_UNUSED_ENTRY(146), + UNIC_RX_PTYPE_UNUSED_ENTRY(147), + UNIC_RX_PTYPE_UNUSED_ENTRY(148), + UNIC_RX_PTYPE_UNUSED_ENTRY(149), + UNIC_RX_PTYPE_UNUSED_ENTRY(150), + UNIC_RX_PTYPE_UNUSED_ENTRY(151), + UNIC_RX_PTYPE_UNUSED_ENTRY(152), + UNIC_RX_PTYPE_UNUSED_ENTRY(153), + UNIC_RX_PTYPE_UNUSED_ENTRY(154), + UNIC_RX_PTYPE_UNUSED_ENTRY(155), + UNIC_RX_PTYPE_UNUSED_ENTRY(156), + UNIC_RX_PTYPE_UNUSED_ENTRY(157), + UNIC_RX_PTYPE_UNUSED_ENTRY(158), + UNIC_RX_PTYPE_UNUSED_ENTRY(159), + UNIC_RX_PTYPE_UNUSED_ENTRY(160), + UNIC_RX_PTYPE_UNUSED_ENTRY(161), + UNIC_RX_PTYPE_UNUSED_ENTRY(162), + UNIC_RX_PTYPE_UNUSED_ENTRY(163), + UNIC_RX_PTYPE_UNUSED_ENTRY(164), + UNIC_RX_PTYPE_UNUSED_ENTRY(165), + UNIC_RX_PTYPE_UNUSED_ENTRY(166), + UNIC_RX_PTYPE_UNUSED_ENTRY(167), + UNIC_RX_PTYPE_UNUSED_ENTRY(168), + UNIC_RX_PTYPE_UNUSED_ENTRY(169), + UNIC_RX_PTYPE_UNUSED_ENTRY(170), + UNIC_RX_PTYPE_UNUSED_ENTRY(171), + UNIC_RX_PTYPE_UNUSED_ENTRY(172), + UNIC_RX_PTYPE_UNUSED_ENTRY(173), + UNIC_RX_PTYPE_UNUSED_ENTRY(174), + UNIC_RX_PTYPE_UNUSED_ENTRY(175), + UNIC_RX_PTYPE_UNUSED_ENTRY(176), + UNIC_RX_PTYPE_UNUSED_ENTRY(177), + UNIC_RX_PTYPE_UNUSED_ENTRY(178), + UNIC_RX_PTYPE_UNUSED_ENTRY(179), + UNIC_RX_PTYPE_UNUSED_ENTRY(180), + UNIC_RX_PTYPE_UNUSED_ENTRY(181), + UNIC_RX_PTYPE_UNUSED_ENTRY(182), + UNIC_RX_PTYPE_UNUSED_ENTRY(183), + UNIC_RX_PTYPE_UNUSED_ENTRY(184), + UNIC_RX_PTYPE_UNUSED_ENTRY(185), + UNIC_RX_PTYPE_UNUSED_ENTRY(186), + UNIC_RX_PTYPE_UNUSED_ENTRY(187), + UNIC_RX_PTYPE_UNUSED_ENTRY(188), + UNIC_RX_PTYPE_UNUSED_ENTRY(189), + UNIC_RX_PTYPE_UNUSED_ENTRY(190), + UNIC_RX_PTYPE_UNUSED_ENTRY(191), + UNIC_RX_PTYPE_UNUSED_ENTRY(192), + UNIC_RX_PTYPE_UNUSED_ENTRY(193), + UNIC_RX_PTYPE_UNUSED_ENTRY(194), + UNIC_RX_PTYPE_UNUSED_ENTRY(195), + UNIC_RX_PTYPE_UNUSED_ENTRY(196), + UNIC_RX_PTYPE_UNUSED_ENTRY(197), + UNIC_RX_PTYPE_UNUSED_ENTRY(198), + UNIC_RX_PTYPE_UNUSED_ENTRY(199), + UNIC_RX_PTYPE_UNUSED_ENTRY(200), + UNIC_RX_PTYPE_UNUSED_ENTRY(201), + UNIC_RX_PTYPE_UNUSED_ENTRY(202), + UNIC_RX_PTYPE_UNUSED_ENTRY(203), + UNIC_RX_PTYPE_UNUSED_ENTRY(204), + UNIC_RX_PTYPE_UNUSED_ENTRY(205), + UNIC_RX_PTYPE_UNUSED_ENTRY(206), + UNIC_RX_PTYPE_UNUSED_ENTRY(207), + UNIC_RX_PTYPE_UNUSED_ENTRY(208), + UNIC_RX_PTYPE_UNUSED_ENTRY(209), + UNIC_RX_PTYPE_UNUSED_ENTRY(210), + UNIC_RX_PTYPE_UNUSED_ENTRY(211), + UNIC_RX_PTYPE_UNUSED_ENTRY(212), + UNIC_RX_PTYPE_UNUSED_ENTRY(213), + UNIC_RX_PTYPE_UNUSED_ENTRY(214), + UNIC_RX_PTYPE_UNUSED_ENTRY(215), + UNIC_RX_PTYPE_UNUSED_ENTRY(216), + UNIC_RX_PTYPE_UNUSED_ENTRY(217), + UNIC_RX_PTYPE_UNUSED_ENTRY(218), + UNIC_RX_PTYPE_UNUSED_ENTRY(219), + UNIC_RX_PTYPE_UNUSED_ENTRY(220), + UNIC_RX_PTYPE_UNUSED_ENTRY(221), + UNIC_RX_PTYPE_UNUSED_ENTRY(222), + UNIC_RX_PTYPE_UNUSED_ENTRY(223), + UNIC_RX_PTYPE_UNUSED_ENTRY(224), + UNIC_RX_PTYPE_UNUSED_ENTRY(225), + UNIC_RX_PTYPE_UNUSED_ENTRY(226), + UNIC_RX_PTYPE_UNUSED_ENTRY(227), + UNIC_RX_PTYPE_UNUSED_ENTRY(228), + UNIC_RX_PTYPE_UNUSED_ENTRY(229), + UNIC_RX_PTYPE_UNUSED_ENTRY(230), + UNIC_RX_PTYPE_UNUSED_ENTRY(231), + UNIC_RX_PTYPE_UNUSED_ENTRY(232), + UNIC_RX_PTYPE_UNUSED_ENTRY(233), + UNIC_RX_PTYPE_UNUSED_ENTRY(234), + UNIC_RX_PTYPE_UNUSED_ENTRY(235), + UNIC_RX_PTYPE_UNUSED_ENTRY(236), + UNIC_RX_PTYPE_UNUSED_ENTRY(237), + UNIC_RX_PTYPE_UNUSED_ENTRY(238), + UNIC_RX_PTYPE_UNUSED_ENTRY(239), + UNIC_RX_PTYPE_UNUSED_ENTRY(240), + UNIC_RX_PTYPE_UNUSED_ENTRY(241), + UNIC_RX_PTYPE_UNUSED_ENTRY(242), + UNIC_RX_PTYPE_UNUSED_ENTRY(243), + UNIC_RX_PTYPE_UNUSED_ENTRY(244), + UNIC_RX_PTYPE_UNUSED_ENTRY(245), + UNIC_RX_PTYPE_UNUSED_ENTRY(246), + UNIC_RX_PTYPE_UNUSED_ENTRY(247), + UNIC_RX_PTYPE_UNUSED_ENTRY(248), + UNIC_RX_PTYPE_UNUSED_ENTRY(249), + UNIC_RX_PTYPE_UNUSED_ENTRY(250), + UNIC_RX_PTYPE_UNUSED_ENTRY(251), + UNIC_RX_PTYPE_UNUSED_ENTRY(252), + UNIC_RX_PTYPE_UNUSED_ENTRY(253), + UNIC_RX_PTYPE_UNUSED_ENTRY(254), + UNIC_RX_PTYPE_UNUSED_ENTRY(255), +}; + +static inline u16 unic_get_rqe_depth(struct unic_rq *rq) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + + return unic_dev->channels.rqe_depth; +} + +static inline u16 unic_get_rqe_mask(struct unic_rq *rq) +{ + return unic_get_rqe_depth(rq) - 1; +} + +static inline u16 unic_get_rx_buff_len(struct unic_rq *rq) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + + return unic_dev->channels.rx_buff_len; +} + +static unsigned int unic_page_order(struct unic_channels *channels) +{ +#if (PAGE_SIZE < 8192) + if (channels->rx_buff_len > (PAGE_SIZE / 2)) + return 1; +#endif + return 0; +} + +static void unic_init_jfr_ctx(struct unic_rq *rq, u16 rqe_depth, + u16 channels_num, u32 idx, u32 tid) +{ + dma_addr_t rqe_dma_addr = rq->rqe_base_dma_addr; + dma_addr_t db_dma_addr = rq->sw_db.db_dma_addr; + struct unic_jfr_ctx *ctx = &rq->jfr_ctx; + u32 jfcn = idx + channels_num; + + ctx->state = UNIC_JFR_STATE_READY; + ctx->token_en = 0; + ctx->rqe_shift = ilog2(roundup_pow_of_two(rqe_depth)); + ctx->jfcn_l = jfcn & (u32)UNIC_JFR_JFCN_L_VALID_BIT; + ctx->jfcn_h = (jfcn >> UNIC_JFR_JFCN_H_OFFSET) & + (u32)UNIC_JFR_JFCN_H_VALID_BIT; + ctx->rqe_base_addr_l = (rqe_dma_addr >> UNIC_RQE_VA_L_PAGE_4K_OFFSET) & + UNIC_RQE_VA_L_VALID_BIT; + ctx->rqe_base_addr_h = (rqe_dma_addr >> UNIC_RQE_VA_H_PAGE_4K_OFFSET) & + UNIC_RQE_VA_H_VALID_BIT; + ctx->rqe_token_id_l = tid & (u32)UNIC_RQE_TOKEN_ID_L_MASK; + ctx->rqe_token_id_h = (tid >> UNIC_RQE_TOKEN_ID_H_OFFSET) & + (u32)UNIC_RQE_TOKEN_ID_H_MASK; + ctx->idx_que_addr_l = unic_get_rx_buff_len(rq); + + ctx->pld_token_id = tid; + ctx->pi = rqe_depth; + ctx->ci = 0; + ctx->record_db_en = UNIC_RECORD_EN; + ctx->record_db_addr_l = (db_dma_addr >> UNIC_RQE_DB_VA_L_64B_OFFSET) & + UNIC_RQE_DB_VA_L_VALID_BIT; + ctx->record_db_addr_m = (db_dma_addr >> UNIC_RQE_DB_VA_M_64B_OFFSET) & + UNIC_RQE_DB_VA_M_VALID_BIT; + ctx->record_db_addr_h = (db_dma_addr >> UNIC_RQE_DB_VA_H_64B_OFFSET) & + UNIC_RQE_DB_VA_H_VALID_BIT; + ctx->cqeie = 0; + ctx->cqesz = 0; +} + +static int unic_rq_alloc_resource(struct unic_dev *unic_dev, struct unic_rq *rq) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + u16 rqe_depth; + u32 size; + int ret; + + rqe_depth = unic_dev->channels.rqe_depth; + rq->rqe_info = devm_kcalloc(&adev->dev, rqe_depth, + sizeof(struct unic_rqe_info), GFP_KERNEL); + if (!rq->rqe_info) { + dev_err(adev->dev.parent, "failed to alloc unic rqe info.\n"); + ret = -ENOMEM; + goto err_alloc_rqe_info; + } + + size = rqe_depth * sizeof(struct unic_rqe); + rq->rqe = dma_alloc_coherent(adev->dev.parent, size, + &rq->rqe_base_dma_addr, GFP_KERNEL); + if (!rq->rqe) { + dev_err(adev->dev.parent, "failed to dma alloc unic rqe.\n"); + ret = -ENOMEM; + goto err_alloc_rqe; + } + + rq->sw_db.db_addr = dma_alloc_coherent(adev->dev.parent, + UNIC_JFR_DB_SIZE, + &rq->sw_db.db_dma_addr, + GFP_KERNEL); + if (!rq->sw_db.db_addr) { + dev_err(adev->dev.parent, + "failed to dma alloc software db addr.\n"); + ret = -ENOMEM; + goto err_alloc_sw_db; + } + + return 0; + +err_alloc_sw_db: + dma_free_coherent(adev->dev.parent, size, rq->rqe, + rq->rqe_base_dma_addr); +err_alloc_rqe: + devm_kfree(&adev->dev, rq->rqe_info); +err_alloc_rqe_info: + return ret; +} + +static void unic_rq_free_resource(struct unic_dev *unic_dev, struct unic_rq *rq) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_sw_db *sw_db = &rq->sw_db; + u32 size; + + size = unic_dev->channels.rqe_depth * sizeof(struct unic_rqe); + dma_free_coherent(rq->parent_dev, UNIC_JFR_DB_SIZE, sw_db->db_addr, + sw_db->db_dma_addr); + dma_free_coherent(rq->parent_dev, size, rq->rqe, + rq->rqe_base_dma_addr); + devm_kfree(&adev->dev, rq->rqe_info); +} + +static int unic_mbx_create_jfr_context(struct auxiliary_device *adev, + struct unic_rq *rq, u32 idx) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mailbox for create jfr context, idx = %u.\n", + idx); + return -ENOMEM; + } + + memcpy(mailbox->buf, &rq->jfr_ctx, sizeof(struct unic_jfr_ctx)); + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_CREATE_JFR_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to post create jfr ctx mbx, idx = %u, ret = %d.\n", + idx, ret); + + ubase_free_cmd_mailbox(adev, mailbox); + return ret; +} + +static void unic_modify_jfr_state(struct auxiliary_device *adev, u32 idx, + enum unic_jfr_state state) +{ + struct unic_jfr_ctx *ctx, *ctx_mask; + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mbx for modify jfr(%u) state\n", idx); + return; + } + + ctx = (struct unic_jfr_ctx *)mailbox->buf; + ctx_mask = ctx + 1; + memset(ctx_mask, 0xff, sizeof(struct unic_jfr_ctx)); + ctx->state = state; + ctx_mask->state = 0; + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_MODIFY_JFR_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to upgrade jfr(%u) ctx state, ret=%d.\n", idx, + ret); + + ubase_free_cmd_mailbox(adev, mailbox); +} + +static int unic_get_last_rqe_idx(struct unic_dev *unic_dev, struct unic_rq *rq) +{ + u8 jfc_shift = unic_dev->channels.rq_jfc_shift; + u16 buff_len = unic_get_rx_buff_len(rq); + struct unic_cq *cq = rq->cq; + u16 pkt_len, rqe_num; + union unic_cqe *cqe; + int rqe_idx = -1; + u32 cq_mask, ci; + + ci = cq->ci; + cq_mask = unic_get_rq_cqe_mask(unic_dev); + dma_rmb(); /* Memory barrier before read cqe */ + cqe = &cq->cqe[ci & cq_mask]; + while (unic_cqe_owner_is_soft(jfc_shift, ci, cqe->rx.owner)) { + pkt_len = cqe->rx.packet_len; + rqe_num = DIV_ROUND_UP(pkt_len, buff_len); + rqe_idx = cqe->rx.start_rqe_idx + rqe_num; + ci++; + cqe = &cq->cqe[ci & cq_mask]; + } + + return rqe_idx; +} + +static int unic_jfr_wait_flush_done(struct unic_dev *unic_dev, u32 idx, + struct unic_rq *rq) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + struct unic_jfr_ctx *ctx; + int rqe_idx = -1; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) + return -ENOMEM; + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_QUERY_JFR_CONTEXT, 0); + + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + goto out; + + ctx = (struct unic_jfr_ctx *)mailbox->buf; + if (rq->ci == ctx->ci) + goto out; + + rqe_idx = unic_get_last_rqe_idx(unic_dev, rq); + if ((rqe_idx != -1) && ((rqe_idx & USHRT_MAX) == ctx->ci)) + goto out; + + ret = -EBUSY; +out: + ubase_free_cmd_mailbox(adev, mailbox); + return ret; +} + +static void unic_mbx_destroy_jfr_context(struct auxiliary_device *adev, u32 idx) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mailbox for destroy jfr(%u) context.\n", + idx); + return; + } + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_DESTROY_JFR_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to post destroy jfr(%u) mailbox, ret: %d.\n", + idx, ret); + + ubase_free_cmd_mailbox(adev, mailbox); +} + +static void unic_multi_jfr_wait_flush(struct unic_dev *unic_dev, u32 num) +{ +#define UNIC_WAIT_JFR_FLUSH_DONE_TIME 100 +#define UNIC_WAIT_JFR_FLUSH_EVERY_TIME 10 + + struct auxiliary_device *adev = unic_dev->comdev.adev; + unsigned long end_jiffies, fd_bitmap = 0; + struct unic_channel *c; + bool timeout = false; + u32 i, fd_cnt = 0; + + end_jiffies = jiffies + msecs_to_jiffies(UNIC_WAIT_JFR_FLUSH_DONE_TIME); + while (!timeout) { + /* check the last result after waiting enough timeout */ + if (time_is_before_eq_jiffies(end_jiffies)) + timeout = true; + + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + if (test_bit(i, &fd_bitmap)) + continue; + + c->ret = unic_jfr_wait_flush_done(unic_dev, i, c->rq); + if (c->ret) + continue; + + fd_cnt++; + set_bit(i, &fd_bitmap); + } + + if (fd_cnt == num) + return; + + msleep(UNIC_WAIT_JFR_FLUSH_EVERY_TIME); + } + + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + if (!test_bit(i, &fd_bitmap)) + dev_err(adev->dev.parent, + "wait jfr(%u) flush timeout, ret=%d\n", i, + c->ret); + } +} + +static void unic_destroy_multi_jfr_context(struct unic_dev *unic_dev, u32 num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + u32 i; + + for (i = 0; i < num; i++) + unic_modify_jfr_state(adev, i, UNIC_JFR_STATE_ERROR); + + unic_multi_jfr_wait_flush(unic_dev, num); + + for (i = 0; i < num; i++) + unic_mbx_destroy_jfr_context(adev, i); +} + +static int unic_rq_create_page_pool(struct unic_dev *unic_dev, u16 rqe_depth, + struct unic_rq *rq) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channels *channels = &unic_dev->channels; + u32 order = unic_page_order(channels); + struct page_pool_params pp_params = { + .flags = PP_FLAG_DMA_MAP | PP_FLAG_PAGE_FRAG | + PP_FLAG_DMA_SYNC_DEV, + .order = order, + .pool_size = rqe_depth * channels->rx_buff_len / + (PAGE_SIZE << order), + .nid = dev_to_node(adev->dev.parent), + .dev = adev->dev.parent, + .dma_dir = DMA_FROM_DEVICE, + .max_len = PAGE_SIZE << order, + .offset = 0, + }; + + rq->page_pool = page_pool_create(&pp_params); + if (IS_ERR(rq->page_pool)) { + dev_warn(adev->dev.parent, + "failed to create page pool, ret = %ld.\n", + PTR_ERR(rq->page_pool)); + rq->page_pool = NULL; + return -ENOMEM; + } + + return 0; +} + +static void unic_rq_free_page_pool(struct unic_rq *rq) +{ + page_pool_destroy(rq->page_pool); + rq->page_pool = NULL; +} + +static int unic_page_pool_alloc_frag(struct unic_dev *unic_dev, + struct unic_rq *rq, + struct unic_rqe_info *rqe_info) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channels *channels = &unic_dev->channels; + + rqe_info->page_offset = 0; + rqe_info->p = page_pool_dev_alloc_frag(rq->page_pool, + &rqe_info->page_offset, + channels->rx_buff_len); + if (unlikely(!rqe_info->p)) { + dev_err(adev->dev.parent, "failed to alloc page pool frag.\n"); + return -ENOMEM; + } + + rqe_info->buf = page_address(rqe_info->p); + rqe_info->rqe_dma_addr = page_pool_get_dma_addr(rqe_info->p); + + return 0; +} + +static void unic_page_pool_put_frags(struct unic_rq *rq, u32 rqe_num) +{ + u16 rqe_mask = unic_get_rqe_mask(rq); + u32 i; + + for (i = 0; i < rqe_num; i++, rq->ci++) + page_pool_put_full_page(rq->page_pool, + rq->rqe_info[rq->ci & rqe_mask].p, false); +} + +static void unic_rq_free_rx_buffers(struct unic_rq *rq) +{ + u32 rqe_num = rq->pi < rq->ci ? rq->pi + UNIC_RQ_CI_REVERSE - rq->ci : + rq->pi - rq->ci; + + unic_page_pool_put_frags(rq, rqe_num); + unic_rq_free_page_pool(rq); +} + +static int unic_rq_fill_rx_buffers(struct unic_dev *unic_dev, struct unic_rq *rq) +{ +#define UNIC_RESCHED_BD_NUM 1024 + + struct unic_rqe *rqe; + u16 i, rqe_depth; + int ret; + + rqe_depth = unic_dev->channels.rqe_depth; + ret = unic_rq_create_page_pool(unic_dev, rqe_depth, rq); + if (ret) + return ret; + + rqe = rq->rqe; + for (i = 0; i < rqe_depth; i++, rq->pi++) { + ret = unic_page_pool_alloc_frag(unic_dev, rq, &rq->rqe_info[i]); + if (ret) + goto err_alloc_page; + + rqe[i].buff_addr = cpu_to_le64(rq->rqe_info[i].rqe_dma_addr + + rq->rqe_info[i].page_offset); + if (!(i % UNIC_RESCHED_BD_NUM)) + cond_resched(); + } + + return 0; + +err_alloc_page: + unic_rq_free_rx_buffers(rq); + + return ret; +} + +int unic_create_rq(struct unic_dev *unic_dev, u32 idx) +{ + struct unic_channel *channel = &unic_dev->channels.c[idx]; + struct auxiliary_device *adev = unic_dev->comdev.adev; + u16 rqe_depth = unic_dev->channels.rqe_depth; + struct unic_rq *rq; + u16 channels_num; + int ret; + + rq = devm_kzalloc(&adev->dev, sizeof(*rq), GFP_KERNEL); + if (!rq) + return -ENOMEM; + + rq->parent_dev = adev->dev.parent; + + ret = unic_rq_alloc_resource(unic_dev, rq); + if (ret) + goto err_alloc_rq_buf; + + rq->netdev = unic_dev->comdev.netdev; + rq->queue_index = idx & U16_MAX; + ret = unic_rq_fill_rx_buffers(unic_dev, rq); + if (ret) + goto err_fill_rqe_buff; + + channels_num = unic_dev->channels.num; + unic_init_jfr_ctx(rq, rqe_depth, channels_num, idx, unic_dev->tid); + + *(u16 *)(rq->sw_db.db_addr) = rqe_depth; + + ret = unic_mbx_create_jfr_context(adev, rq, idx); + if (ret) + goto err_fill_send_rq_mbx; + + channel->rq = rq; + return 0; + +err_fill_send_rq_mbx: + unic_rq_free_rx_buffers(rq); +err_fill_rqe_buff: + unic_rq_free_resource(unic_dev, rq); +err_alloc_rq_buf: + devm_kfree(&adev->dev, rq); + return ret; +} + +static void unic_free_multi_rq_resource(struct unic_dev *unic_dev, u32 num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channel *channel; + u32 i; + + for (i = 0; i < num; i++) { + channel = &unic_dev->channels.c[i]; + if (!channel->rq) { + dev_err(adev->dev.parent, + "failed to get channel rq(%u).\n", i); + continue; + } + unic_rq_free_rx_buffers(channel->rq); + unic_rq_free_resource(unic_dev, channel->rq); + devm_kfree(&adev->dev, channel->rq); + channel->rq = NULL; + } +} + +void unic_destroy_rq(struct unic_dev *unic_dev, u32 num) +{ + if (!num) + return; + + if (!__unic_resetting(unic_dev)) + unic_destroy_multi_jfr_context(unic_dev, num); + + unic_free_multi_rq_resource(unic_dev, num); +} + +static void unic_handle_rx_csum(struct net_device *netdev, struct sk_buff *skb, + union unic_cqe *cqe, struct unic_rq *rq) +{ +#define UNIC_RX_CQE_L3L4P_ENABLE 1 + + u16 csum = cqe->rx.unknown_pkt_l2_checksum; + + skb->ip_summed = CHECKSUM_NONE; + + if (!(netdev->features & NETIF_F_RXCSUM)) + return; + + if (!(cqe->rx.l3l4p & UNIC_RX_CQE_L3L4P_ENABLE)) + return; + + if (unlikely(cqe->rx.l3_err || cqe->rx.l4_err || cqe->rx.ol3_err || + cqe->rx.ol4_err)) { + unic_rq_stats_inc(rq, l3_l4_csum_err); + return; + } + + skb->ip_summed = unic_rx_ptype_tbl[cqe->rx.ptype].ip_summed; + skb->csum_level = unic_rx_ptype_tbl[cqe->rx.ptype].csum_level; + if (skb->ip_summed == CHECKSUM_COMPLETE) { + unic_rq_stats_inc(rq, csum_complete); + skb->csum = csum_unfold((__force __sum16)csum); + } +} + +#ifdef CONFIG_UB_UNIC_UBL +static __be16 unic_assign_ub_proto(struct net_device *netdev, + struct sk_buff *skb, u32 l3_type) +{ + u8 ub_type; + + if (l3_type == UNIC_L3_TYPE_IPV4) + ub_type = UB_IPV4_CFG_TYPE; + else if (l3_type == UNIC_L3_TYPE_IPV6) + ub_type = UB_IPV6_CFG_TYPE; + else + ub_type = UB_NOIP_CFG_TYPE; + + return ubl_type_trans(skb, netdev, ub_type); +} +#endif + +static int unic_handle_cqe(struct unic_rq *rq, union unic_cqe *cqe) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + struct net_device *netdev = rq->netdev; + struct sk_buff *skb = rq->skb; + enum pkt_hash_types rss_type; + + if (unlikely(!cqe->rx.packet_len || cqe->rx.l2_err || cqe->rx.trunc)) { + if (!cqe->rx.packet_len) + unic_rq_stats_inc(rq, err_pkt_len_cnt); + else if (cqe->rx.l2_err) + unic_rq_stats_inc(rq, l2_err); + else + unic_rq_stats_inc(rq, trunc_cnt); + + return -EFAULT; + } + +#ifdef CONFIG_UB_UNIC_UBL + if (unic_dev_ubl_supported(unic_dev)) + skb->protocol = unic_assign_ub_proto(netdev, skb, + unic_rx_ptype_tbl[cqe->rx.ptype].l3_type); + else + skb->protocol = eth_type_trans(skb, netdev); +#else + skb->protocol = eth_type_trans(skb, netdev); +#endif + + unic_handle_rx_csum(netdev, skb, cqe, rq); + + rss_type = unic_rx_ptype_tbl[cqe->rx.ptype].hash_type; + + skb_set_hash(skb, le32_to_cpu(cqe->rx.rss_hash), rss_type); + + skb_record_rx_queue(skb, rq->queue_index); + + return 0; +} + +static void unic_fill_skb_frags(struct unic_rq *rq, struct sk_buff *skb, + struct unic_rqe_info *rqe_info, + u32 frag_index, u32 frag_size) +{ + u16 buff_len = unic_get_rx_buff_len(rq); + u32 page_offset = rqe_info->page_offset; + struct page *page = rqe_info->p; + + dma_sync_single_for_cpu(rq->parent_dev, + rqe_info->rqe_dma_addr + page_offset, frag_size, + DMA_FROM_DEVICE); + + skb_add_rx_frag(skb, frag_index, page, page_offset, frag_size, + buff_len); +} + +static int unic_add_skb_frags(struct unic_rq *rq, struct napi_struct *napi, + u16 pkt_len, u32 pull_len) +{ + u16 buff_len = unic_get_rx_buff_len(rq); + u16 rqe_num = DIV_ROUND_UP(pkt_len, buff_len); + u16 rqe_mask = unic_get_rqe_mask(rq); + struct sk_buff *head_skb = rq->skb; + u16 i, frag_size, frag_index = 0; + struct unic_rqe_info *rqe_info; + struct sk_buff *skb = head_skb; + struct sk_buff *new_skb; + u16 last_ci = rq->ci; + + /* add skb first frag */ + frag_size = rqe_num == 1 ? pkt_len : buff_len; + rqe_info = &rq->rqe_info[last_ci++ & rqe_mask]; + rqe_info->page_offset += pull_len; + unic_fill_skb_frags(rq, skb, rqe_info, frag_index++, + frag_size - pull_len); + + for (i = 1; i < rqe_num; i++, frag_index++, last_ci++) { + if (unlikely(frag_index >= MAX_SKB_FRAGS)) { + new_skb = napi_alloc_skb(napi, 0); + if (unlikely(!new_skb)) { + unic_rq_stats_inc(rq, alloc_skb_err); + return -ENOMEM; + } + + skb_mark_for_recycle(new_skb); + frag_index = 0; + + if (skb == head_skb) + skb_shinfo(skb)->frag_list = new_skb; + else + skb->next = new_skb; + + skb = new_skb; + } + + pkt_len = pkt_len - buff_len; + frag_size = i < rqe_num - 1 ? buff_len : pkt_len; + + if (unlikely(skb != head_skb)) { + head_skb->len += frag_size; + head_skb->data_len += frag_size; + head_skb->truesize += buff_len; + } + + rqe_info = &rq->rqe_info[last_ci & rqe_mask]; + unic_fill_skb_frags(rq, skb, rqe_info, frag_index, frag_size); + } + + return 0; +} + +static u32 unic_get_skb_linear_len(struct unic_rq *rq, u8 *va, + struct unic_dev *unic_dev) +{ + u32 pull_len; + + if (unic_dev_ubl_supported(unic_dev)) + pull_len = UNIC_RX_HEAD_SIZE; + else + pull_len = eth_get_headlen(rq->netdev, va, UNIC_RX_HEAD_SIZE); + + return pull_len; +} + +static int unic_create_skb(struct unic_rq *rq, struct napi_struct *napi, + u16 pkt_len) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + struct unic_rqe_info *rqe_info; + struct sk_buff *skb; + u32 pull_len; + u16 rqe_mask; + int ret; + u8 *va; + + rqe_mask = unic_get_rqe_mask(rq); + rqe_info = &rq->rqe_info[rq->ci & rqe_mask]; + + skb = napi_alloc_skb(napi, UNIC_RX_HEAD_SIZE); + if (unlikely(!skb)) { + unic_rq_stats_inc(rq, alloc_skb_err); + return -ENOMEM; + } + + rq->skb = skb; + va = rqe_info->buf + rqe_info->page_offset; + + if (pkt_len <= UNIC_RX_HEAD_SIZE) { + dma_sync_single_for_cpu(rq->parent_dev, + rqe_info->rqe_dma_addr + rqe_info->page_offset, + ALIGN(pkt_len, sizeof(long)), + DMA_FROM_DEVICE); + + memcpy(__skb_put(skb, pkt_len), va, ALIGN(pkt_len, sizeof(long))); + + page_pool_put_full_page(rq->page_pool, rqe_info->p, false); + return 0; + } + + skb_mark_for_recycle(skb); + + pull_len = unic_get_skb_linear_len(rq, va, unic_dev); + __skb_put(skb, pull_len); + ret = unic_add_skb_frags(rq, napi, pkt_len, pull_len); + if (unlikely(ret)) { + dev_kfree_skb_any(rq->skb); + rq->skb = NULL; + return ret; + } + + memcpy(skb->data, va, ALIGN(pull_len, sizeof(long))); + + return 0; +} + +static void unic_fix_rq_ci(struct unic_rq *rq, union unic_cqe *cqe) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + u32 start_rqe_idx = cqe->rx.start_rqe_idx; + u32 put_page_num; + + if (unlikely(rq->ci != cqe->rx.start_rqe_idx)) { + if (net_ratelimit()) + unic_warn(unic_dev, + "queue_index(%u) sw_rq_ci(%hu) hw_rq_ci(%u) do not match.\n", + rq->queue_index, rq->ci, start_rqe_idx); + if (start_rqe_idx < rq->ci) + start_rqe_idx += UNIC_RQ_CI_REVERSE; + put_page_num = start_rqe_idx - rq->ci; + unic_page_pool_put_frags(rq, put_page_num); + rq->pending_buf += put_page_num; + rq->ci = cqe->rx.start_rqe_idx; + } +} + +static int unic_rx_construct_skb(struct unic_rq *rq, struct napi_struct *napi, + union unic_cqe *cqe, u64 *bytes) +{ + u16 buff_len = unic_get_rx_buff_len(rq); + u16 pkt_len = cqe->rx.packet_len; + u16 rqe_num; + int ret; + + rq->cq->ci++; + if (unlikely(cqe->rx.doi)) { + unic_rq_stats_inc(rq, doi_cnt); + return -EFAULT; + } + + unic_fix_rq_ci(rq, cqe); + rqe_num = DIV_ROUND_UP(pkt_len, buff_len); + rq->pending_buf += rqe_num; + ret = unic_create_skb(rq, napi, pkt_len); + if (unlikely(ret)) + goto release_rx_buffer; + + ret = unic_handle_cqe(rq, cqe); + if (unlikely(ret)) + goto destroy_skb; + + *bytes += pkt_len; + rq->ci += rqe_num; + + return 0; + +destroy_skb: + dev_kfree_skb_any(rq->skb); + rq->skb = NULL; +release_rx_buffer: + unic_page_pool_put_frags(rq, rqe_num); + return ret; +} + +static bool unic_refill_rx_buffers(struct unic_rq *rq) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + u16 rqe_mask = unic_get_rqe_mask(rq); + struct unic_rqe_info *rqe_info; + u16 pd_buff = rq->pending_buf; + u32 i, rq_ci = rq->ci; + struct unic_rqe *rqe; + bool ret = false; + + if (unlikely(rq_ci < pd_buff)) + rq_ci += UNIC_RQ_CI_REVERSE; + + for (i = rq_ci - pd_buff; i < rq_ci; i++) { + rqe_info = &rq->rqe_info[i & rqe_mask]; + if (unlikely(unic_page_pool_alloc_frag(unic_dev, rq, rqe_info))) { + unic_rq_stats_inc(rq, alloc_frag_err); + ret = true; + break; + } + + rqe = &rq->rqe[i & rqe_mask]; + rqe->buff_addr = rqe_info->rqe_dma_addr + rqe_info->page_offset; + rq->pi++; + } + + if (likely(i > rq_ci - pd_buff)) { + rq->pending_buf = rq_ci - i; + *(u16 *)(rq->sw_db.db_addr) = rq->pi; + } + + return ret; +} + +void unic_send_skb_to_stack(struct unic_channel *c, struct sk_buff *skb) +{ + struct napi_struct *napi = &c->napi; + + if (skb_has_frag_list(skb)) + napi_gro_flush(napi, false); + + napi_gro_receive(napi, skb); +} + +int unic_poll_rx(struct unic_channel *c, int budget, + void (*rx_fn)(struct unic_channel *, struct sk_buff *)) +{ +#define UNIC_RX_BUFFER_WRITE 16 + + struct unic_rq *rq = c->rq; + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + u8 jfc_shift = unic_dev->channels.rq_jfc_shift; + struct unic_cq *cq = rq->cq; + struct napi_struct *napi; + u32 last_ci = cq->ci; + bool failure = false; + union unic_cqe *cqe; + int packets = 0; + u64 bytes = 0; + u32 cq_mask; + + napi = &c->napi; + cq_mask = unic_get_rq_cqe_mask(unic_dev); + + while (packets < budget) { + if (rq->pending_buf >= UNIC_RX_BUFFER_WRITE) { + unic_cq_doorbell(cq, last_ci); + last_ci = cq->ci; + failure = failure || unic_refill_rx_buffers(rq); + } + + dma_rmb(); /* Memory barrier before read cqe */ + cqe = &cq->cqe[cq->ci & cq_mask]; + if (!unic_cqe_owner_is_soft(jfc_shift, cq->ci, cqe->rx.owner)) + break; + + trace_unic_rx_cqe(rq->netdev, cq, rq->pi, rq->ci, cq_mask); + if (unic_rx_construct_skb(rq, napi, cqe, &bytes)) + break; + + rx_fn(c, rq->skb); + + rq->skb = NULL; + + packets++; + } + + if (rq->pending_buf) { + unic_cq_doorbell(cq, last_ci); + failure = failure || unic_refill_rx_buffers(rq); + } + + u64_stats_update_begin(&rq->syncp); + rq->stats.packets += (u64)packets; + rq->stats.bytes += bytes; + u64_stats_update_end(&rq->syncp); + + return failure ? budget : packets; +} + +static void unic_clear_rq_buffers(struct unic_rq *rq, union unic_cqe *cqe) +{ + u16 buff_len = unic_get_rx_buff_len(rq); + u16 pkt_len, rqe_num; + + if (cqe->rx.doi) { + unic_rq_stats_inc(rq, doi_cnt); + return; + } + + unic_fix_rq_ci(rq, cqe); + pkt_len = le16_to_cpu(cqe->rx.packet_len); + rqe_num = DIV_ROUND_UP(pkt_len, buff_len); + unic_page_pool_put_frags(rq, rqe_num); + + rq->pending_buf += rqe_num; +} + +void unic_clear_rq(struct unic_rq *rq) +{ + struct unic_dev *unic_dev = netdev_priv(rq->netdev); + u8 jfc_shift = unic_dev->channels.rq_jfc_shift; + u32 cq_mask = unic_get_rq_cqe_mask(unic_dev); + struct unic_cq *cq = rq->cq; + u32 last_ci = cq->ci; + union unic_cqe *cqe; + + cqe = &cq->cqe[cq->ci & cq_mask]; + while (unic_cqe_owner_is_soft(jfc_shift, cq->ci, cqe->rx.owner)) { + unic_clear_rq_buffers(rq, cqe); + cq->ci++; + cqe = &cq->cqe[cq->ci & cq_mask]; + } + + unic_cq_doorbell(cq, last_ci); + + unic_refill_rx_buffers(rq); +} diff --git a/drivers/net/ub/unic/unic_rx.h b/drivers/net/ub/unic/unic_rx.h new file mode 100644 index 0000000000000000000000000000000000000000..591cf4cbb6f179c87f56c373bb22b33f6df42eb4 --- /dev/null +++ b/drivers/net/ub/unic/unic_rx.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_RX_H__ +#define __UNIC_RX_H__ + +#include + +struct unic_dev; + +#define UNIC_RQE_VA_L_PAGE_4K_OFFSET 12 +#define UNIC_RQE_VA_L_VALID_BIT GENMASK(19, 0) +#define UNIC_RQE_VA_H_OFFSET 20 +#define UNIC_RQE_VA_H_PAGE_4K_OFFSET (UNIC_RQE_VA_H_OFFSET + \ + UNIC_RQE_VA_L_PAGE_4K_OFFSET) +#define UNIC_RQE_VA_H_VALID_BIT GENMASK(31, 0) + +#define UNIC_JFR_JFCN_L_VALID_BIT GENMASK(11, 0) +#define UNIC_JFR_JFCN_H_OFFSET 12U +#define UNIC_JFR_JFCN_H_VALID_BIT GENMASK(7, 0) + +#define UNIC_RQE_DB_VA_L_64B_OFFSET 6 +#define UNIC_RQE_DB_VA_L_VALID_BIT GENMASK(23, 0) +#define UNIC_RQE_DB_VA_M_OFFSET 24 +#define UNIC_RQE_DB_VA_M_64B_OFFSET \ + (UNIC_RQE_DB_VA_M_OFFSET + UNIC_RQE_DB_VA_L_64B_OFFSET) +#define UNIC_RQE_DB_VA_M_VALID_BIT GENMASK(31, 0) +#define UNIC_RQE_DB_VA_H_OFFSET 32 +#define UNIC_RQE_DB_VA_H_64B_OFFSET \ + (UNIC_RQE_DB_VA_H_OFFSET + UNIC_RQE_DB_VA_M_64B_OFFSET) +#define UNIC_RQE_DB_VA_H_VALID_BIT GENMASK(1, 0) + +#define UNIC_RQE_TOKEN_ID_L_MASK GENMASK(13, 0) +#define UNIC_RQE_TOKEN_ID_H_OFFSET 14U +#define UNIC_RQE_TOKEN_ID_H_MASK GENMASK(5, 0) + +#define UNIC_JFR_DB_SIZE 4 + +#define UNIC_RX_BUF_LEN_2K 2048 +#define UNIC_RX_BUF_LEN_4K 4096 + +#define unic_rq_stats_inc(rq, cnt) do { \ + typeof(rq) _rq = (rq); \ + u64_stats_update_begin(&(_rq)->syncp); \ + ((_rq)->stats.cnt)++; \ + u64_stats_update_end(&(_rq)->syncp); \ + } while (0) + +enum unic_jfr_state { + UNIC_JFR_STATE_RESET, + UNIC_JFR_STATE_READY, + UNIC_JFR_STATE_ERROR, + UNIC_JFR_STATE_RESERVED, +}; + +struct unic_channel; + +struct unic_rx_ptype { + u32 ptype : 8; + u32 csum_level : 2; + u32 ip_summed : 2; + u32 l3_type : 4; + u32 valid : 1; + u32 hash_type : 3; + u32 resv : 12; +}; + +struct unic_rq_stats { + u64 alloc_skb_err; + u64 bytes; + u64 packets; + u64 err_pkt_len_cnt; + u64 doi_cnt; + u64 trunc_cnt; + u64 multicast; + u64 l2_err; + u64 l3_l4_csum_err; + u64 csum_complete; + u64 alloc_frag_err; +}; + +struct unic_jfr_ctx { + /* DW0 */ + u32 state : 2; + u32 limit_wl : 2; + u32 rqe_size_shift : 3; + u32 token_en : 1; + u32 rqe_shift : 4; + u32 rnr_timer : 5; + u32 record_db_en : 1; + u32 rqe_token_id_l : 14; + /* DW1 */ + u32 rqe_token_id_h : 6; + u32 type : 3; + u32 rsv : 3; + u32 rqe_base_addr_l : 20; + /* DW2 */ + u32 rqe_base_addr_h; + /* DW3 */ + u32 rqe_position : 1; + u32 pld_position : 1; + u32 pld_token_id : 20; + u32 rsv1 : 10; + /* DW4 */ + u32 token_value; + /* DW5 */ + u32 user_data_l; + /* DW6 */ + u32 user_data_h; + /* DW7 */ + u32 pi : 16; + u32 ci : 16; + /* DW8 */ + u32 idx_que_addr_l; + /* DW9 */ + u32 idx_que_addr_h : 20; + u32 jfcn_l : 12; + /* DW10 */ + u32 jfcn_h : 8; + u32 record_db_addr_l : 24; + /* DW11 */ + u32 record_db_addr_m; + /* DW12 */ + u32 record_db_addr_h : 2; + u32 cqeie : 1; + u32 cqesz : 1; + u32 rsv3 : 28; + /* DW13 - DW15 */ + u32 rsv4[3]; +}; + +struct unic_rqe { + __le64 rsv; + __le64 buff_addr; +}; + +struct unic_rqe_info { + void *buf; + struct page *p; + dma_addr_t rqe_dma_addr; + u32 page_offset; +}; + +struct unic_sw_db { + void *db_addr; + dma_addr_t db_dma_addr; +}; + +struct unic_rq { + struct device *parent_dev; + struct unic_rq_stats stats; + struct u64_stats_sync syncp; + struct unic_jfr_ctx jfr_ctx; + struct unic_rqe *rqe; + dma_addr_t rqe_base_dma_addr; + struct unic_rqe_info *rqe_info; + struct unic_sw_db sw_db; + struct unic_cq *cq; + struct sk_buff *skb; + u16 pi; /* the start of next to product */ + u16 ci; /* the start of next to consume */ + u16 queue_index; + u16 pending_buf; + struct net_device *netdev; + struct page_pool *page_pool; +}; + +int unic_create_rq(struct unic_dev *unic_dev, u32 idx); +void unic_destroy_rq(struct unic_dev *unic_dev, u32 idx); +int unic_poll_rx(struct unic_channel *c, int budget, + void (*rx_fn)(struct unic_channel *, struct sk_buff *)); +void unic_clear_rq(struct unic_rq *rq); +void unic_send_skb_to_stack(struct unic_channel *c, struct sk_buff *skb); + +#endif diff --git a/drivers/net/ub/unic/unic_stats.c b/drivers/net/ub/unic/unic_stats.c new file mode 100644 index 0000000000000000000000000000000000000000..8167bb6a6fcac9171211a70c9ef13797611d0683 --- /dev/null +++ b/drivers/net/ub/unic/unic_stats.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include + +#include "unic.h" +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_netdev.h" +#include "unic_stats.h" + +static void unic_get_fec_stats_total(struct unic_dev *unic_dev, u8 stats_flags, + struct ethtool_fec_stats *fec_stats) +{ + struct unic_fec_stats_item *total = &unic_dev->stats.fec_stats.total; + + if (stats_flags & UNIC_FEC_CORR_BLOCKS) + fec_stats->corrected_blocks.total = total->corr_blocks; + if (stats_flags & UNIC_FEC_UNCORR_BLOCKS) + fec_stats->uncorrectable_blocks.total = total->uncorr_blocks; + if (stats_flags & UNIC_FEC_CORR_BITS) + fec_stats->corrected_bits.total = total->corr_bits; +} + +static void unic_get_ubl_fec_stats(struct unic_dev *unic_dev, + struct ethtool_fec_stats *fec_stats) +{ + u32 fec_mode = unic_dev->hw.mac.fec_mode; + u8 stats_flags = 0; + + switch (fec_mode) { + case ETHTOOL_FEC_RS: + stats_flags = UNIC_FEC_UNCORR_BLOCKS | UNIC_FEC_CORR_BITS; + unic_get_fec_stats_total(unic_dev, stats_flags, fec_stats); + break; + default: + unic_err(unic_dev, + "fec stats is not supported in mode(0x%x).\n", + fec_mode); + break; + } +} + +void unic_get_fec_stats(struct net_device *ndev, + struct ethtool_fec_stats *fec_stats) +{ + struct unic_dev *unic_dev = netdev_priv(ndev); + + if (!unic_dev_fec_stats_supported(unic_dev) || + unic_dev->hw.mac.fec_mode == ETHTOOL_FEC_OFF) + return; + + if (unic_update_fec_stats(unic_dev)) + return; + + if (unic_dev_ubl_supported(unic_dev)) + unic_get_ubl_fec_stats(unic_dev, fec_stats); +} diff --git a/drivers/net/ub/unic/unic_stats.h b/drivers/net/ub/unic/unic_stats.h new file mode 100644 index 0000000000000000000000000000000000000000..d9d9f0b0ee3bff98e99906161307680d3f9699cd --- /dev/null +++ b/drivers/net/ub/unic/unic_stats.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_STATS_H__ +#define __UNIC_STATS_H__ + +#include +#include +#include +#include + +#define UNIC_FEC_CORR_BLOCKS BIT(0) +#define UNIC_FEC_UNCORR_BLOCKS BIT(1) +#define UNIC_FEC_CORR_BITS BIT(2) + +void unic_get_fec_stats(struct net_device *ndev, + struct ethtool_fec_stats *fec_stats); + +#endif /* __UNIC_STATS_H__ */ diff --git a/drivers/net/ub/unic/unic_trace.h b/drivers/net/ub/unic/unic_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..b6cec3303175d00dd492aa0169effdd4d5cd992a --- /dev/null +++ b/drivers/net/ub/unic/unic_trace.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +/* This must be outside ifdef _UNIC_TRACE_H_ */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM unic + +#if !defined(__UNIC_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __UNIC_TRACE_H__ + +#include + +#define trace_tx_max_sqebb_num 8 +#define trace_cqe_num (sizeof(union unic_cqe) / sizeof(u32)) +#define trace_sqebb_num(sqebb_num) \ + (sizeof(struct unic_sqebb) * (sqebb_num) / sizeof(u32)) + +#define trace_nlmsghdr_arr_size (sizeof(struct nlmsghdr) / sizeof(u32)) +#define trace_ifaddrmsg_arr_size (sizeof(struct ifaddrmsg) / sizeof(u32)) +#define trace_attrbuf_arr_size 16 + +DECLARE_EVENT_CLASS(unic_cqe_template, + TP_PROTO(const struct net_device *netdev, + const struct unic_cq *cq, u16 pi, u16 ci, + u32 cqe_mask), + TP_ARGS(netdev, cq, pi, ci, cqe_mask), + + TP_STRUCT__entry(__field(u32, jfcn) + __field(u32, cqe_mask) + __field(u16, pi) + __field(u16, ci) + __field(u32, cq_ci) + __field(u8, cqe_num) + __array(u32, cqe, trace_cqe_num) + __string(devname, netdev->name) + ), + + TP_fast_assign(__entry->jfcn = cq->jfcn; + __entry->cqe_mask = cqe_mask; + __entry->pi = pi; + __entry->ci = ci; + __entry->cq_ci = cq->ci; + __entry->cqe_num = unic_get_cqe_size() / sizeof(u32); + memcpy(__entry->cqe, + &cq->cqe[cq->ci & __entry->cqe_mask], + unic_get_cqe_size()); + __assign_str(devname, netdev->name); + ), + + TP_printk("%s-%u-%u/%u cqe(%u): %s", + __get_str(devname), __entry->jfcn, __entry->pi, + __entry->ci, __entry->cq_ci, + __print_array(__entry->cqe, __entry->cqe_num, + sizeof(u32)) + ) +); + +DEFINE_EVENT(unic_cqe_template, unic_tx_cqe, + TP_PROTO(const struct net_device *netdev, const struct unic_cq *cq, + u16 pi, u16 ci, u32 cqe_mask), + TP_ARGS(netdev, cq, pi, ci, cqe_mask)); + +DEFINE_EVENT(unic_cqe_template, unic_rx_cqe, + TP_PROTO(const struct net_device *netdev, const struct unic_cq *cq, + u16 pi, u16 ci, u32 cqe_mask), + TP_ARGS(netdev, cq, pi, ci, cqe_mask)); + +TRACE_EVENT(unic_tx_sqe, + TP_PROTO(struct unic_sq *sq, u16 sqebb_num, u16 sqebb_mask, + bool doorbell), + TP_ARGS(sq, sqebb_num, sqebb_mask, doorbell), + + TP_STRUCT__entry(__field(u32, jfcn) + __field(u16, pi) + __field(u16, ci) + __field(u16, sqebb_num) + __field(bool, doorbell) + __field(u16, real_pi) + __array(u32, sqebb, trace_sqebb_num(trace_tx_max_sqebb_num)) + __string(devname, sq->netdev->name) + ), + + TP_fast_assign(__entry->jfcn = sq->cq->jfcn; + __entry->pi = sq->pi; + __entry->ci = sq->ci; + __entry->sqebb_num = sqebb_num; + __entry->doorbell = doorbell; + __entry->real_pi = sq->pi & sqebb_mask; + if (__entry->real_pi + sqebb_num - 1 > sqebb_mask) { + memcpy(__entry->sqebb, &sq->sqebb[__entry->real_pi], + (sqebb_mask - __entry->real_pi + 1) * + sizeof(struct unic_sqebb)); + memcpy(&__entry->sqebb[sqebb_mask - __entry->real_pi + 1], + &sq->sqebb[0], + (__entry->real_pi + sqebb_num - 1 - sqebb_mask) * + sizeof(struct unic_sqebb)); + } else { + memcpy(__entry->sqebb, &sq->sqebb[__entry->real_pi], + sqebb_num * sizeof(struct unic_sqebb)); + } + unic_mask_key_words(__entry->sqebb); + __assign_str(devname, sq->netdev->name); + ), + + TP_printk("%s-%u-%u/%u-%d sqe: %s", + __get_str(devname), __entry->jfcn, __entry->pi, + __entry->ci, __entry->doorbell, + __print_array(__entry->sqebb, + trace_sqebb_num(__entry->sqebb_num), + sizeof(u32)) + ) +); + +TRACE_EVENT(unic_ip_req_skb, + TP_PROTO(const struct net_device *netdev, const struct sk_buff *skb, + const struct nlmsghdr *nh, const struct ifaddrmsg *info, + const char *attrbuf), + TP_ARGS(netdev, skb, nh, info, attrbuf), + + TP_STRUCT__entry(__field(u32, len) + __array(u32, nl_hdr, trace_nlmsghdr_arr_size) + __array(u32, ifa_info, trace_ifaddrmsg_arr_size) + __array(u32, attrs_buf, trace_attrbuf_arr_size) + __string(devname, netdev->name) + ), + + TP_fast_assign(__entry->len = skb->len; + memcpy(__entry->nl_hdr, nh, sizeof(struct nlmsghdr)); + memcpy(__entry->ifa_info, info, sizeof(struct ifaddrmsg)); + memcpy(__entry->attrs_buf, attrbuf, + sizeof(u32) * trace_attrbuf_arr_size); + __assign_str(devname, netdev->name); + ), + + TP_printk("%s netlink: len=%u nl_hdr=%s ifa_info=%s attrs_buf=%s", + __get_str(devname), __entry->len, + __print_array(__entry->nl_hdr, trace_nlmsghdr_arr_size, + sizeof(u32)), + __print_array(__entry->ifa_info, trace_ifaddrmsg_arr_size, + sizeof(u32)), + __print_array(__entry->attrs_buf, trace_attrbuf_arr_size, + sizeof(u32)) + ) +); +#endif /* _UNIC_TRACE_H_ */ + +/* This must be outside ifdef _UNIC_TRACE_H */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE unic_trace +#include diff --git a/drivers/net/ub/unic/unic_tx.c b/drivers/net/ub/unic/unic_tx.c new file mode 100644 index 0000000000000000000000000000000000000000..f0e97f1c3184ea4ca0391a445b6531405c3f5e51 --- /dev/null +++ b/drivers/net/ub/unic/unic_tx.c @@ -0,0 +1,1348 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_UB_UNIC_UBL +#include +#endif +#include + +#include "unic_dev.h" +#include "unic_hw.h" +#include "unic_tx.h" + +/* When use tracepoint, must define "CREATE_TRACE_POINTS" before include the + * trace header file. + * If many source file need include the same header file, the + * "CREATE_TRACE_POINTS" only define once. + */ +#define CREATE_TRACE_POINTS +#include "unic_trace.h" + +#define UNIC_CSUM_OFFLOAD_DISABLE 0 +#define UNIC_CSUM_OFFLOAD_ENABLE 1 +#define IP_VERSION_IPV4 0x4 +#define IP_VERSION_IPV6 0x6 + +#define UNIC_CC_DEFAULT_FECN_MODE 0x4000 +#define UNIC_SGE_MAX_PAYLOAD UINT_MAX +#define UNIC_MIN_TX_LEN 60U +#define UNIC_SQE_CTRL_SECTION_NUM 2 +#define UNIC_SQE_MAX_SGE_NUM 18 +#define UNIC_SQEBB_MAX_SGE_NUM 4 +#define UNIC_HEADER_LEN_2B_OFFSET 1 +#define UNIC_HEADER_LEN_4B_OFFSET 2 +#define UNIC_SKB_FRAGS_START_INDEX 1 +#define UNIC_SQEBB_POINT_REVERSE (USHRT_MAX + 1) +#define UNIC_RCV_SEND_MAX_DIFF_VAL 512U + +#define unic_sqebb_cnt(sge_num) DIV_ROUND_UP((sge_num), 4) + +static inline u16 unic_get_sqe_depth(struct unic_sq *sq) +{ + struct unic_dev *unic_dev = netdev_priv(sq->netdev); + + return unic_dev->channels.sqebb_depth; +} + +static inline u16 unic_get_sqe_mask(struct unic_sq *sq) +{ + return unic_get_sqe_depth(sq) - 1; +} + +static u16 unic_get_spare_sqebb(struct unic_sq *sq) +{ + u16 sqe_depth = unic_get_sqe_depth(sq); + u32 pi = sq->pi; + + if (unlikely(sq->pi < sq->ci)) + pi += UNIC_SQEBB_POINT_REVERSE; + + return sqe_depth - (pi - sq->ci); +} + +static void unic_unmap_skb_buffer(struct unic_sq *sq, u32 info_index, + u8 skb_map_num, u16 sqebb_mask) +{ + struct unic_dev *unic_dev = netdev_priv(sq->netdev); + struct unic_sqe_ctrl_section *ctrl; + struct unic_sqe_sge_section *sge; + struct device *dev; + u8 i; + + ctrl = (struct unic_sqe_ctrl_section *)&sq->sqebb[info_index & sqebb_mask]; + sge = (struct unic_sqe_sge_section *)(ctrl + 1); + dev = &unic_dev->comdev.adev->dev; + dma_unmap_single(dev->parent, sge->address, sge->length, DMA_TO_DEVICE); + + for (i = UNIC_SKB_FRAGS_START_INDEX; i < skb_map_num; i++) { + if (!((i + UNIC_SQE_CTRL_SECTION_NUM) % UNIC_SQEBB_MAX_SGE_NUM)) { + info_index++; + sge = (struct unic_sqe_sge_section *) + &sq->sqebb[info_index & sqebb_mask]; + } else { + sge++; + } + + dma_unmap_page(dev->parent, sge->address, sge->length, DMA_TO_DEVICE); + } +} + +static void unic_consume_skb(struct sk_buff *skb, u32 info_index, + struct unic_sq *sq, int budget, u16 sqebb_mask) +{ + struct unic_sqe_ctrl_section *ctrl; + + ctrl = (struct unic_sqe_ctrl_section *)&sq->sqebb[info_index & sqebb_mask]; + unic_unmap_skb_buffer(sq, info_index, ctrl->sge_num, sqebb_mask); + + napi_consume_skb(skb, budget); +} + +static bool unic_check_hw_ci_valid(u16 hw_ci, u16 sq_ci, struct unic_sq *sq) +{ + u16 sqebb_mask = unic_get_sqe_mask(sq); + struct unic_sqe_ctrl_section *ctrl; + u16 sqebb_cnt, sw_ci; + + ctrl = (struct unic_sqe_ctrl_section *)&sq->sqebb[sq_ci & sqebb_mask]; + sqebb_cnt = unic_sqebb_cnt(ctrl->sge_num + UNIC_SQE_CTRL_SECTION_NUM); + sw_ci = sq_ci + sqebb_cnt - 1; + if (unlikely(sw_ci != hw_ci)) { + unic_sq_stats_inc(sq, ci_mismatch); + return false; + } + + return true; +} + +static void unic_reclaim_single_sqe_space(struct unic_sq *sq, u16 sqebb_mask, + u16 *sq_ci) +{ + struct unic_sqe_ctrl_section *ctrl; + u16 ci = *sq_ci & sqebb_mask; + u8 sge_num, sqebb_cnt; + + ctrl = (struct unic_sqe_ctrl_section *)&sq->sqebb[ci]; + sge_num = ctrl->sge_num; + sqebb_cnt = unic_sqebb_cnt(sge_num + UNIC_SQE_CTRL_SECTION_NUM); + if (unlikely(ci + sqebb_cnt - 1 > sqebb_mask)) { + memset(ctrl, 0, (sqebb_mask - ci + 1) * sizeof(struct unic_sqebb)); + memset(&sq->sqebb[0], 0, + (ci + sqebb_cnt - 1 - sqebb_mask) * sizeof(struct unic_sqebb)); + } else { + memset(ctrl, 0, sqebb_cnt * sizeof(struct unic_sqebb)); + } + + *sq_ci += sqebb_cnt; +} + +static void unic_flush_unused_sqe(struct unic_sq *sq, u16 sqebb_mask, + u16 *sq_ci) +{ + struct sk_buff *skb; + + while (*sq_ci != sq->pi) { + skb = sq->skbs[*sq_ci & sqebb_mask]; + unic_consume_skb(skb, *sq_ci, sq, 0, sqebb_mask); + unic_reclaim_single_sqe_space(sq, sqebb_mask, sq_ci); + } +} + +static bool unic_check_hw_ci_late(struct unic_sq *sq, u16 sq_ci) +{ + u32 effect_num, actual_num; + + if (likely(!sq->check_ci_late)) + return false; + + effect_num = sq->pi < sq->start_pi ? + (u32)sq->pi + UNIC_SQEBB_POINT_REVERSE - (u32)sq->start_pi : + (u32)sq->pi - (u32)sq->start_pi; + + actual_num = sq->pi < sq_ci ? + (u32)sq->pi + UNIC_SQEBB_POINT_REVERSE - (u32)sq_ci : + (u32)sq->pi - (u32)sq_ci; + + if (unlikely(actual_num > effect_num)) { + unic_sq_stats_inc(sq, drop_cnt); + return true; + } + + sq->check_ci_late = false; + return false; +} + +static bool unic_reclaim_sq_space(struct unic_sq *sq, int budget, u64 *bytes, + u64 *packets, bool clear) +{ + struct net_device *netdev = sq->netdev; + struct unic_dev *unic_dev = netdev_priv(netdev); + u8 jfc_shift = unic_dev->channels.sq_jfc_shift; + u16 sqebb_mask = unic_get_sqe_mask(sq); + union unic_cqe *cqe = sq->cq->cqe; + struct unic_cq *cq = sq->cq; + u32 cq_mask, cq_ci = cq->ci; + bool reclaimed = false; + struct sk_buff *skb; + u16 sq_ci = sq->ci; + + cq_mask = unic_get_sq_cqe_mask(unic_dev); + cqe = &cq->cqe[cq->ci & cq_mask]; + while (unic_cqe_owner_is_soft(jfc_shift, cq->ci, cqe->tx.owner)) { + trace_unic_tx_cqe(netdev, cq, sq->pi, sq_ci, cq_mask); + if (unlikely(cqe->tx.fd)) { + cq->ci++; + unic_sq_stats_inc(sq, fd_cnt); + unic_flush_unused_sqe(sq, sqebb_mask, &sq_ci); + reclaimed = true; + break; + } + + if (unlikely(!unic_check_hw_ci_valid(cqe->tx.raw_ci, sq_ci, sq))) + break; + + skb = sq->skbs[sq_ci & sqebb_mask]; + + if (!clear && likely(!unic_check_hw_ci_late(sq, sq_ci))) { + *bytes += skb->len; + (*packets)++; + } + + unic_consume_skb(skb, sq_ci, sq, budget, sqebb_mask); + unic_reclaim_single_sqe_space(sq, sqebb_mask, &sq_ci); + + reclaimed = true; + cq->ci++; + cqe = &cq->cqe[cq->ci & cq_mask]; + } + + unic_cq_doorbell(cq, cq_ci); + sq->ci = sq_ci; + return reclaimed; +} + +void unic_poll_tx(struct unic_sq *sq, int budget) +{ +#define UNIC_MIN_SPARE_SQEBB DIV_ROUND_UP(UNIC_SQE_CTRL_SECTION_NUM + \ + UNIC_SQE_MAX_SGE_NUM, \ + UNIC_SQEBB_MAX_SGE_NUM) + + struct net_device *netdev = sq->netdev; + struct netdev_queue *dev_queue; + struct unic_dev *unic_dev; + u64 packets = 0; + u64 bytes = 0; + + if (unlikely(!unic_reclaim_sq_space(sq, budget, &bytes, &packets, false))) + return; + + u64_stats_update_begin(&sq->syncp); + sq->stats.bytes += bytes; + sq->stats.packets += packets; + u64_stats_update_end(&sq->syncp); + + dev_queue = netdev_get_tx_queue(netdev, sq->queue_index); + netdev_tx_completed_queue(dev_queue, packets, bytes); + + if (unlikely(netif_carrier_ok(netdev) && + unic_get_spare_sqebb(sq) >= UNIC_MIN_SPARE_SQEBB)) { + unic_dev = netdev_priv(netdev); + if (netif_tx_queue_stopped(dev_queue) && + !test_bit(UNIC_STATE_DOWN, &unic_dev->state)) { + netif_tx_wake_queue(dev_queue); + unic_sq_stats_inc(sq, restart_queue); + } + } +} + +static u8 unic_sq_get_sl(struct unic_dev *unic_dev, u32 idx) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_adev_qos *qos = ubase_get_adev_qos(adev); + struct unic_vl *vl = &unic_dev->channels.vl; + int i; + + for (i = 0; i < unic_dev->channels.rss_vl_num; i++) + if (idx < vl->queue_offset[i] + vl->queue_count[i]) + break; + + return i >= unic_dev->channels.rss_vl_num ? + vl->vl_sl[qos->nic_vl[0]] : + vl->vl_sl[qos->nic_vl[i]]; +} + +static void unic_init_jfs_ctx(struct unic_dev *unic_dev, struct unic_sq *sq, + u32 idx, u32 tid) +{ + struct unic_jfs_ctx *ctx = &sq->jfs_ctx; + + ctx->ta_timeout = UNIC_TIMEOUT_8S; + ctx->type = UNIC_RAW_TYPE; + ctx->sqe_bb_shift = unic_dev->channels.sqebb_shift; + ctx->state = UNIC_JFS_STATE_READY; + ctx->sl = unic_sq_get_sl(unic_dev, idx) & UNIC_SQE_SL_BIT; + ctx->jfs_mode = UNIC_JFS; + ctx->sqe_token_id_l = tid & (u32)UNIC_SQE_TOKEN_ID_L_MASK; + ctx->sqe_token_id_h = (tid >> UNIC_SQE_TOKEN_ID_H_OFFSET) & + (u32)UNIC_SQE_TOKEN_ID_H_MASK; + ctx->sqe_base_addr_l = (sq->sqebb_dma_addr >> UNIC_SQE_VA0_OFFSET) & + UNIC_SQE_VA0_VALID_BIT; + ctx->sqe_base_addr_h = (sq->sqebb_dma_addr >> UNIC_SQE_VA1_OFFSET) & + UNIC_SQE_VA1_VALID_BIT; + ctx->tx_jfcn = idx; + ctx->sqe_pld_tokenid = tid; + ctx->avail_sgmt_ost = UNIC_AVAIL_SGMT_OST_INIT; +} + +static int unic_mbx_create_jfs_context(struct auxiliary_device *adev, + struct unic_sq *sq, u32 idx) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mailbox for create jfs context, idx = %u.\n", + idx); + return -ENOMEM; + } + + memcpy(mailbox->buf, &sq->jfs_ctx, sizeof(struct unic_jfs_ctx)); + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_CREATE_JFS_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to post create jfs ctx mbx, idx = %u, ret = %d.\n", + idx, ret); + + ubase_free_cmd_mailbox(adev, mailbox); + + return ret; +} + +static void unic_modify_jfs_state_to_error(struct auxiliary_device *adev, + u32 idx) +{ + struct unic_jfs_ctx *ctx, *ctx_mask; + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mbx for set jfs state.\n"); + return; + } + + ctx = (struct unic_jfs_ctx *)mailbox->buf; + ctx_mask = ctx + 1; + memset(ctx_mask, 0xff, sizeof(struct unic_jfs_ctx)); + ctx->state = UNIC_JFS_STATE_ERROR; + ctx_mask->state = 0; + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_MODIFY_JFS_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to set jfs state, ret=%d.\n", ret); + + ubase_free_cmd_mailbox(adev, mailbox); +} + +static u16 unic_get_ta_timeout_ms(u8 ta_timeout) +{ +#define UNIC_DEFINE_128MS 128 +#define UNIC_DEFINE_1S 1000 +#define UNIC_DEFINE_8S 8000 +#define UNIC_DEFINE_64S 64000 + + switch (ta_timeout) { + case UNIC_TIMEOUT_128MS: + return UNIC_DEFINE_128MS; + case UNIC_TIMEOUT_1S: + return UNIC_DEFINE_1S; + case UNIC_TIMEOUT_8S: + return UNIC_DEFINE_8S; + default: + return UNIC_DEFINE_64S; + } +} + +static bool unic_jfs_schedule_complete(struct unic_jfs_ctx *ctx) +{ + u16 rcv_send_diff; + + rcv_send_diff = le16_to_cpu(ctx->next_rcv_ssn) - + le16_to_cpu(ctx->next_send_ssn); + if (ctx->state == UNIC_JFS_STATE_READY && ctx->PI == ctx->CI && + rcv_send_diff < UNIC_RCV_SEND_MAX_DIFF_VAL) + return true; + + if (ctx->state == UNIC_JFS_STATE_ERROR && + rcv_send_diff < UNIC_RCV_SEND_MAX_DIFF_VAL) + return true; + + return false; +} + +static int unic_jfs_flush_prepare(struct auxiliary_device *adev, u32 idx) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + struct unic_jfs_ctx *ctx; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) + return -ENOMEM; + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_QUERY_JFS_CONTEXT, 0); + + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + goto out; + + ctx = (struct unic_jfs_ctx *)mailbox->buf; + if (!unic_jfs_schedule_complete(ctx)) + ret = -EBUSY; +out: + ubase_free_cmd_mailbox(adev, mailbox); + return ret; +} + +static inline bool unic_jfs_flush_ssn_vld(struct unic_jfs_ctx *ctx) +{ + u16 rcv_send_diff = le16_to_cpu(ctx->next_rcv_ssn) - + le16_to_cpu(ctx->next_send_ssn); + + return ctx->flush_ssn_vld && + (rcv_send_diff < UNIC_RCV_SEND_MAX_DIFF_VAL); +} + +static inline bool unic_jfs_flush_cqe_done(struct unic_jfs_ctx *ctx) +{ + return ctx->flush_cqe_done; +} + +static int unic_check_jfs_flush_done(struct auxiliary_device *adev, u32 idx, + bool (*check_fd)(struct unic_jfs_ctx *ctx)) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + struct unic_jfs_ctx *ctx; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) + return -ENOMEM; + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_QUERY_JFS_CONTEXT, 0); + + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + goto out; + + ctx = (struct unic_jfs_ctx *)mailbox->buf; + if (!check_fd(ctx)) + ret = -EBUSY; + +out: + ubase_free_cmd_mailbox(adev, mailbox); + return ret; +} + +static void unic_multi_jfs_wait_flush(struct unic_dev *unic_dev, u32 num, + u16 timeout_ms, u32 start_idx) +{ +#define UNIC_WAIT_FLUSH_EVERY_STEP_TIME 50 +#define UNIC_WAIT_TIME_AFTER_FLUSH_DONE 100 + + struct auxiliary_device *adev = unic_dev->comdev.adev; + unsigned long end_jiffies, fd_bitmap = 0; + struct unic_channel *c; + bool timeout = false; + u32 i, fd_cnt = 0; + int ret; + + end_jiffies = jiffies + msecs_to_jiffies(timeout_ms); + while (!timeout) { + /* check the last result after waiting enough timeout */ + if (time_is_before_eq_jiffies(end_jiffies)) + timeout = true; + + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + if (test_bit(i, &fd_bitmap)) + continue; + + ret = unic_check_jfs_flush_done(adev, i + start_idx, + unic_jfs_flush_cqe_done); + if (ret) + continue; + + fd_cnt++; + set_bit(i, &fd_bitmap); + } + + if (fd_cnt == num) + break; + + msleep(UNIC_WAIT_FLUSH_EVERY_STEP_TIME); + } + + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + if (test_bit(i, &fd_bitmap)) + continue; + + ret = unic_check_jfs_flush_done(adev, i + start_idx, + unic_jfs_flush_ssn_vld); + if (ret) + dev_err(adev->dev.parent, + "wait jfs(%u) flush timeout, ret=%d.\n", + i + start_idx, ret); + } + + udelay(UNIC_WAIT_TIME_AFTER_FLUSH_DONE); +} + +static void unic_multi_jfs_flush_prepare(struct unic_dev *unic_dev, u32 num, + u32 timeout_ms, u32 start_idx) +{ +#define UNIC_WAIT_EVERY_STEP_TIME 50U + + struct auxiliary_device *adev = unic_dev->comdev.adev; + unsigned long end_jiffies, fd_bitmap = 0; + struct unic_channel *c; + bool timeout = false; + u32 i, fd_cnt = 0; + + end_jiffies = jiffies + msecs_to_jiffies(timeout_ms); + while (!timeout) { + /* check the last result after waiting enough timeout */ + if (time_is_before_eq_jiffies(end_jiffies)) + timeout = true; + + for (i = 0; i < num; i++) { + if (test_bit(i, &fd_bitmap)) + continue; + + c = &unic_dev->channels.c[i]; + c->ret = unic_jfs_flush_prepare(adev, i + start_idx); + if (c->ret) + continue; + + fd_cnt++; + set_bit(i, &fd_bitmap); + } + + if (fd_cnt == num) + return; + + msleep(UNIC_WAIT_EVERY_STEP_TIME); + } + + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + if (!test_bit(i, &fd_bitmap)) + dev_err(adev->dev.parent, + "wait jfs(%u) flush prepare timeout, ret=%d.\n", + i + start_idx, c->ret); + } +} + +static inline void unic_modify_multi_jfs_state(struct auxiliary_device *adev, + u32 num, u32 start_idx) +{ + u32 i; + + for (i = 0; i < num; i++) + unic_modify_jfs_state_to_error(adev, i + start_idx); +} + +static void unic_mbx_destroy_jfs_context(struct auxiliary_device *adev, u32 idx) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mbx for destroy jfs context, i=%u.\n", + idx); + goto out; + } + + ubase_fill_mbx_attr(&attr, idx, UBASE_MB_DESTROY_JFS_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to post destroy jfs ctx mbx, ret=%d, i=%u.\n", + ret, idx); + +out: + ubase_free_cmd_mailbox(adev, mailbox); +} + +static inline void unic_destroy_multi_jfs_context(struct auxiliary_device *adev, + u32 num, u32 start_idx) +{ + u32 i; + + for (i = 0; i < num; i++) + unic_mbx_destroy_jfs_context(adev, i + start_idx); +} + +static void unic_destroy_multi_jfs(struct unic_dev *unic_dev, u32 num, + u32 start_idx) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + u32 timeout_ms; + +#if defined(UNIC_FPGA_COMPILE) + timeout_ms = unic_get_ta_timeout_ms(UNIC_TIMEOUT_64S); +#else + timeout_ms = unic_get_ta_timeout_ms(UNIC_TIMEOUT_8S); +#endif + + unic_multi_jfs_flush_prepare(unic_dev, num, timeout_ms, start_idx); + + unic_modify_multi_jfs_state(adev, num, start_idx); + + unic_multi_jfs_wait_flush(unic_dev, num, timeout_ms, start_idx); + + unic_destroy_multi_jfs_context(adev, num, start_idx); +} + +static int unic_sq_alloc_resource(struct auxiliary_device *adev, + struct unic_sq *sq, u32 sqebb_depth) +{ + u32 size = sqebb_depth * sizeof(struct unic_sqebb); + + sq->skbs = devm_kcalloc(&adev->dev, sqebb_depth, + sizeof(struct sk_buff *), GFP_KERNEL); + if (!sq->skbs) { + dev_err(adev->dev.parent, "failed to alloc unic sq skb buff.\n"); + return -ENOMEM; + } + + sq->sqebb = dma_alloc_coherent(adev->dev.parent, size, + &sq->sqebb_dma_addr, GFP_KERNEL); + if (!sq->sqebb) { + dev_err(adev->dev.parent, "failed to dma alloc unic sqebb.\n"); + goto err_alloc_unic_sqebb; + } + + return 0; + +err_alloc_unic_sqebb: + devm_kfree(&adev->dev, sq->skbs); + + return -ENOMEM; +} + +static void unic_sq_free_resource(struct auxiliary_device *adev, + struct unic_sq *sq, u32 sqebb_depth) +{ + u32 size = sqebb_depth * sizeof(struct unic_sqebb); + u16 sqebb_mask = unic_get_sqe_mask(sq); + + unic_flush_unused_sqe(sq, sqebb_mask, &sq->ci); + dma_free_coherent(adev->dev.parent, size, sq->sqebb, sq->sqebb_dma_addr); + devm_kfree(&adev->dev, sq->skbs); +} + +int unic_create_sq(struct unic_dev *unic_dev, u32 idx) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_resource_space *mem_base = ubase_get_mem_base(adev); + struct ubase_adev_caps *unic_caps = ubase_get_unic_caps(adev); + struct unic_channel *channel = &unic_dev->channels.c[idx]; + struct unic_sq *sq; + u32 jfs_start_idx; + u16 sqebb_depth; + u32 offset; + int ret; + + if (!unic_caps) { + dev_err(adev->dev.parent, "failed to get unic caps.\n"); + return -ENODATA; + } + + if (!mem_base) { + dev_err(adev->dev.parent, "failed to get mem base.\n"); + return -ENODATA; + } + + sq = devm_kzalloc(&adev->dev, sizeof(*sq), GFP_KERNEL); + if (!sq) { + dev_err(adev->dev.parent, "failed to alloc sq.\n"); + return -ENOMEM; + } + + sqebb_depth = unic_dev->channels.sqebb_depth; + ret = unic_sq_alloc_resource(adev, sq, sqebb_depth); + if (ret) + goto err_alloc_res; + + jfs_start_idx = unic_caps->jfs.start_idx; + sq->queue_index = idx & U16_MAX; + + /* The jfs doorbell offset must be consistent with that of the chip. */ + offset = UNIC_JFS_DB_4K_OFFSET; + sq->db_addr = mem_base->addr + UNIC_JFS_DB_BASE_OFFSET + + (idx + jfs_start_idx) * offset; + + sq->netdev = unic_dev->comdev.netdev; + sq->parent_dev = adev->dev.parent; + sq->check_ci_late = false; + + unic_init_jfs_ctx(unic_dev, sq, idx, unic_dev->tid); + + ret = unic_mbx_create_jfs_context(adev, sq, idx + jfs_start_idx); + if (ret) + goto err_mbx_create_jfs_context; + + channel->sq = sq; + return 0; + +err_mbx_create_jfs_context: + unic_sq_free_resource(adev, sq, sqebb_depth); +err_alloc_res: + devm_kfree(&adev->dev, sq); + return ret; +} + +static void unic_free_multi_sq_resource(struct unic_dev *unic_dev, u32 num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channel *channel; + u32 i; + + for (i = 0; i < num; i++) { + channel = &unic_dev->channels.c[i]; + unic_sq_free_resource(adev, channel->sq, + unic_dev->channels.sqebb_depth); + devm_kfree(&adev->dev, channel->sq); + channel->sq = NULL; + } +} + +void unic_destroy_sq(struct unic_dev *unic_dev, u32 num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_adev_caps *unic_caps = ubase_get_unic_caps(adev); + u32 jfs_start_idx; + + if (!num || !unic_caps) + return; + + jfs_start_idx = unic_caps->jfs.start_idx; + + if (!__unic_resetting(unic_dev)) + unic_destroy_multi_jfs(unic_dev, num, jfs_start_idx); + + unic_free_multi_sq_resource(unic_dev, num); +} + +#ifdef CONFIG_UB_UNIC_UBL +static int unic_apply_ub_pkt(struct unic_dev *unic_dev, struct unic_sq *sq, + struct sk_buff *skb) +{ + struct ublhdr *ubl = (struct ublhdr *)skb->data; + + if (unic_dev_ubl_supported(unic_dev)) { + if (unlikely(ubl->cfg == UB_NOIP_CFG_TYPE && + unic_is_port_down(unic_dev))) { + unic_sq_stats_inc(sq, cfg5_drop_cnt); + return -EIO; + } + + if (skb->protocol == htons(ETH_P_IP) || + skb->protocol == htons(ETH_P_IPV6)) + ubl->h_cc = htons(UNIC_CC_DEFAULT_FECN_MODE); + + ubl_rmv_sw_ctype(skb); + } + + return 0; +} +#endif + +static u32 unic_tx_need_sge_num(struct sk_buff *skb) +{ + u32 sge_num = 1, i, size; + skb_frag_t *frag; + + if (likely(skb->len <= UNIC_SGE_MAX_PAYLOAD && !skb_has_frag_list(skb))) + return skb_shinfo(skb)->nr_frags + 1U; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + frag = &skb_shinfo(skb)->frags[i]; + size = skb_frag_size(frag); + if (!size) + continue; + + sge_num++; + } + + return sge_num; +} + +static int unic_maybe_stop_tx(struct net_device *netdev, struct unic_sq *sq, + u32 sge_num) +{ + u32 sqebb_num = unic_sqebb_cnt(sge_num + UNIC_SQE_CTRL_SECTION_NUM); + struct unic_dev *unic_dev = netdev_priv(netdev); + u16 spare_num = unic_get_spare_sqebb(sq); + + if (unlikely(sge_num > UNIC_SQE_MAX_SGE_NUM)) { + unic_sq_stats_inc(sq, over_max_sge_num); + return -ENOMEM; + } + + if (likely(spare_num >= sqebb_num)) + return 0; + + netif_stop_subqueue(netdev, sq->queue_index); + smp_mb(); /* Memory barrier before checking sqebb space */ + + if (unlikely(netif_carrier_ok(netdev) && + !test_bit(UNIC_STATE_DOWN, &unic_dev->state) && + unic_get_spare_sqebb(sq) > sqebb_num)) { + netif_start_subqueue(netdev, sq->queue_index); + return 0; + } + + unic_sq_stats_inc(sq, busy); + + return -EBUSY; +} + +static void unic_fill_ctrl_owner(struct unic_sq *sq, + struct unic_sqe_ctrl_section *ctrl) +{ + struct unic_dev *unic_dev = netdev_priv(sq->netdev); + u16 sqebb_shift = unic_dev->channels.sqebb_shift; + + ctrl->owner = ~(sq->pi >> sqebb_shift & 0x1); +} + +static void unic_fill_ctrl_l3_info(struct unic_sqe_ctrl_section *ctrl, + struct sk_buff *skb, struct unic_sq *sq) +{ +#define be32_to_le32(x) cpu_to_le32(be32_to_cpu(x)) + + struct ipv6hdr *ip6_hdr; + + if (skb->protocol == htons(ETH_P_IP)) { + ctrl->l3_type = UNIC_L3_TYPE_IPV4; + ctrl->dip_type = UNIC_L3_TYPE_IPV4; + ctrl->dip0 = be32_to_le32(ip_hdr(skb)->daddr); + } else if (skb->protocol == htons(ETH_P_IPV6)) { + ip6_hdr = ipv6_hdr(skb); + ctrl->l3_type = UNIC_L3_TYPE_IPV6; + ctrl->dip_type = UNIC_L3_TYPE_IPV6; + ctrl->dip0 = be32_to_le32(ip6_hdr->daddr.s6_addr32[3]); + ctrl->dip1 = be32_to_le32(ip6_hdr->daddr.s6_addr32[2]); + ctrl->dip2 = be32_to_le32(ip6_hdr->daddr.s6_addr32[1]); + ctrl->dip3 = be32_to_le32(ip6_hdr->daddr.s6_addr32[0]); + } else { + ctrl->l3_type = UNIC_L3_TYPE_NON_IP; + } +} + +static u8 unic_get_l4_protocol(struct sk_buff *skb, struct unic_sq *sq) +{ + u8 l4_proto, *l4hdr, *exthdr; + union l3_hdr_info l3hdr; + __be16 frag_off; + + l3hdr.hdr = skb_network_header(skb); + l4hdr = skb_transport_header(skb); + + if (skb->protocol == htons(ETH_P_IP)) { + l4_proto = l3hdr.v4->protocol; + } else { + exthdr = l3hdr.hdr + sizeof(*l3hdr.v6); + l4_proto = l3hdr.v6->nexthdr; + if (l4hdr != exthdr) + ipv6_skip_exthdr(skb, exthdr - skb->data, &l4_proto, + &frag_off); + } + + return l4_proto; +} + +static void unic_set_l2(struct sk_buff *skb, struct unic_sqe_ctrl_section *ctrl, + u8 *l2_hdr, u8 *l3_hdr, struct unic_dev *unic_dev) +{ +#define UNIC_L2_LEN_SUPPLEMENT 4 + + u32 l2_len = l3_hdr - l2_hdr; + + /* skb->data not include the first four bytes of the ub link header. + * But l2_len need include the length of complete ub link. + */ + if (unic_dev_ubl_supported(unic_dev)) + l2_len += UNIC_L2_LEN_SUPPLEMENT; + + /* compute L2 header size, defined in 2 Bytes */ + ctrl->l2hdr_len = cpu_to_le32(l2_len >> UNIC_HEADER_LEN_2B_OFFSET); +} + +static void unic_set_l3(struct sk_buff *skb, struct unic_sqe_ctrl_section *ctrl, + union l3_hdr_info l3, u8 *l4_hdr) +{ + u32 l3_len = l4_hdr - l3.hdr; + + /* compute L3 header size, defined in 4 Bytes */ + ctrl->l3hdr_len = cpu_to_le32(l3_len >> UNIC_HEADER_LEN_4B_OFFSET); + + if (l3.v4->version == IP_VERSION_IPV4) + ctrl->l3_csum = skb_is_gso(skb) ? UNIC_CSUM_OFFLOAD_ENABLE : + UNIC_CSUM_OFFLOAD_DISABLE; +} + +static int unic_set_l4(struct sk_buff *skb, struct unic_sqe_ctrl_section *ctrl, + union l4_hdr_info l4_hdr, u8 l4_proto, + struct unic_dev *unic_dev) +{ + switch (l4_proto) { + case IPPROTO_TCP: + ctrl->l4_csum = UNIC_CSUM_OFFLOAD_ENABLE; + ctrl->l4_type = UNIC_L4_TYPE_TCP; + ctrl->l4hdr_len = l4_hdr.tcp->doff; + break; + case IPPROTO_UDP: + ctrl->l4_csum = UNIC_CSUM_OFFLOAD_ENABLE; + ctrl->l4_type = UNIC_L4_TYPE_UDP; + /* compute inner/normal header size, defined in 4 Bytes */ + ctrl->l4hdr_len = sizeof(*l4_hdr.udp) >> + UNIC_HEADER_LEN_4B_OFFSET; + break; + default: + /* drop the skb tunnel packet if hardware don't support, + * because hardware can't calculate csum when TSO. + */ + if (skb_is_gso(skb)) { + unic_err(unic_dev, + "unknown l4 header tso packets hecksum offload.\n"); + return -EDOM; + } + /* the stack computes the IP header already, + * driver calculate l4 checksum when not TSO. + */ + return skb_checksum_help(skb); + } + + return 0; +} + +static int unic_set_l2l3l4(struct sk_buff *skb, u8 l4_proto, + struct unic_sqe_ctrl_section *ctrl, + struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + u8 *l2_hdr = skb->data; + union l4_hdr_info l4; + union l3_hdr_info l3; + + l3.hdr = skb_network_header(skb); + l4.hdr = skb_transport_header(skb); + + unic_set_l2(skb, ctrl, l2_hdr, l3.hdr, unic_dev); + unic_set_l3(skb, ctrl, l3, l4.hdr); + return unic_set_l4(skb, ctrl, l4, l4_proto, unic_dev); +} + +static int unic_handle_csum_partial(struct unic_sqe_ctrl_section *ctrl, + struct sk_buff *skb, struct unic_sq *sq) +{ + struct net_device *netdev = sq->netdev; + u8 l4_proto; + int ret; + + if (skb->ip_summed != CHECKSUM_PARTIAL) + return 0; + + l4_proto = unic_get_l4_protocol(skb, sq); + ret = unic_set_l2l3l4(skb, l4_proto, ctrl, netdev); + if (unlikely(ret < 0)) { + unic_sq_stats_inc(sq, csum_err); + return ret; + } + + return 0; +} + +static int unic_handle_vlan_info(struct unic_sq *sq, struct sk_buff *skb) +{ + struct vlan_ethhdr *vhdr; + int ret; + + if (!(skb->protocol == htons(ETH_P_8021Q) || skb_vlan_tag_present(skb))) + return 0; + + ret = skb_cow_head(skb, 0); + if (unlikely(ret < 0)) { + unic_sq_stats_inc(sq, vlan_err); + return ret; + } + + vhdr = skb_vlan_eth_hdr(skb); + vhdr->h_vlan_TCI |= cpu_to_be16((skb->priority << VLAN_PRIO_SHIFT) & + VLAN_PRIO_MASK); + + skb->protocol = vlan_get_protocol(skb); + + return 0; +} + +static int unic_handle_ctrl_section(struct sk_buff *skb, struct unic_sq *sq, + struct unic_sqe_ctrl_section *ctrl, + u8 sge_num) +{ + int ret; + + ret = unic_handle_vlan_info(sq, skb); + if (unlikely(ret)) + goto vlan_err; + + unic_fill_ctrl_l3_info(ctrl, skb, sq); + + ret = unic_handle_csum_partial(ctrl, skb, sq); + if (unlikely(ret)) + goto csum_err; + + unic_fill_ctrl_owner(sq, ctrl); + + ctrl->sge_num = sge_num; + + return 0; + +csum_err: + memset(ctrl, 0, sizeof(*ctrl)); +vlan_err: + return ret; +} + +static inline void unic_fill_sge_section(struct unic_sqe_sge_section *sge, u64 addr, + u32 length, u32 tid) +{ + sge->length = length; + sge->token_id = tid; + sge->address = addr; +} + +static inline struct unic_sqe_sge_section * +unic_get_next_sge(u8 *sec_num, struct unic_sqe_sge_section *sge, + struct unic_sq *sq, u16 sqebb_mask) +{ + (*sec_num)++; + if (*sec_num >= UNIC_SQEBB_MAX_SGE_NUM) { + sq->pi++; + *sec_num = 0; + sge = (struct unic_sqe_sge_section *) + &sq->sqebb[sq->pi & sqebb_mask]; + } else { + sge++; + } + + return sge; +} + +static int unic_map_skb_frags(struct unic_sq *sq, struct sk_buff *skb, + u8 *skb_map_num, u8 *sec_num, + struct unic_sqe_sge_section *sge) +{ + struct net_device *netdev = sq->netdev; + u16 sqebb_mask = unic_get_sqe_mask(sq); + struct unic_dev *unic_dev; + struct device *dev; + skb_frag_t *frag; + dma_addr_t dma; + u32 size, i; + + unic_dev = netdev_priv(netdev); + dev = &unic_dev->comdev.adev->dev; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + frag = &skb_shinfo(skb)->frags[i]; + size = skb_frag_size(frag); + if (!size) + continue; + + dma = skb_frag_dma_map(dev->parent, frag, 0, size, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(dev, dma))) { + unic_sq_stats_inc(sq, map_err); + return -ENOMEM; + } + + sge = unic_get_next_sge(sec_num, sge, sq, sqebb_mask); + unic_fill_sge_section(sge, (u64)dma, size, unic_dev->tid); + + (*skb_map_num)++; + } + + return 0; +} + +static void unic_clear_sge_section(struct unic_sq *sq, u16 idx, u8 skb_map_num, + u16 sqebb_mask) +{ + struct unic_sqe_ctrl_section *ctrl; + u16 index = idx & sqebb_mask; + u8 sqebb_cnt; + + ctrl = (struct unic_sqe_ctrl_section *)&sq->sqebb[index]; + sqebb_cnt = unic_sqebb_cnt(skb_map_num + UNIC_SQE_CTRL_SECTION_NUM); + if (unlikely(index + sqebb_cnt - 1 > sqebb_mask)) { + memset(ctrl, 0, (sqebb_mask - index + 1) * sizeof(struct unic_sqebb)); + memset(&sq->sqebb[0], 0, + (index + sqebb_cnt - 1 - sqebb_mask) * sizeof(struct unic_sqebb)); + } else { + memset(ctrl, 0, sqebb_cnt * sizeof(struct unic_sqebb)); + } +} + +static int unic_handle_sge_section(struct unic_sq *sq, struct sk_buff *skb, + struct unic_sqe_sge_section *sge, + u16 sqebb_mask) +{ + u8 sec_num = UNIC_SQE_CTRL_SECTION_NUM; + struct net_device *netdev = sq->netdev; + struct unic_dev *unic_dev; + struct device *dev; + u8 skb_map_num = 0; + u16 pi = sq->pi; + dma_addr_t dma; + u32 size; + + size = skb_headlen(skb); + unic_dev = netdev_priv(netdev); + dev = &unic_dev->comdev.adev->dev; + + dma = dma_map_single(dev->parent, skb->data, size, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(dev, dma))) { + unic_sq_stats_inc(sq, map_err); + return -ENOMEM; + } + + unic_fill_sge_section(sge, (u64)dma, size, unic_dev->tid); + skb_map_num++; + + if (unlikely(unic_map_skb_frags(sq, skb, &skb_map_num, &sec_num, sge))) + goto dma_map_err; + + sq->pi = pi; + return 0; + +dma_map_err: + unic_unmap_skb_buffer(sq, pi, skb_map_num, sqebb_mask); + unic_clear_sge_section(sq, pi, skb_map_num, sqebb_mask); + sq->pi = pi; + return -ENOMEM; +} + +static int unic_handle_sqe(struct sk_buff *skb, struct unic_sq *sq, u8 sge_num) +{ + u16 sqebb_mask = unic_get_sqe_mask(sq); + struct unic_sqe_ctrl_section *ctrl; + struct unic_sqe_sge_section *sge; + int ret; + + ctrl = (struct unic_sqe_ctrl_section *)&sq->sqebb[sq->pi & sqebb_mask]; + ret = unic_handle_ctrl_section(skb, sq, ctrl, sge_num); + if (unlikely(ret)) + return ret; + + sge = (struct unic_sqe_sge_section *)(ctrl + 1); + ret = unic_handle_sge_section(sq, skb, sge, sqebb_mask); + if (unlikely(ret)) + return ret; + + sq->skbs[sq->pi & sqebb_mask] = skb; + + return ret; +} + +static void unic_tx_doorbell(struct unic_sq *sq, u16 sge_num, bool doorbell) +{ + u16 sqebb_num = unic_sqebb_cnt(sge_num + UNIC_SQE_CTRL_SECTION_NUM); + u16 sqebb_mask = unic_get_sqe_mask(sq); + struct unic_jfs_db jfs_db = {0}; + + trace_unic_tx_sqe(sq, sqebb_num, sqebb_mask, doorbell); + + sq->pi += sqebb_num; + + if (!doorbell) { + unic_sq_stats_inc(sq, more); + return; + } + + jfs_db.pi = cpu_to_le16(sq->pi); + writel(*(u32 *)&jfs_db, sq->db_addr); + sq->last_pi = sq->pi; +} + +static void unic_tx_compensate_doorbell(struct unic_sq *sq) +{ + struct unic_jfs_db jfs_db = {0}; + + if (sq->last_pi == sq->pi) + return; + + trace_unic_tx_sqe(sq, 0, unic_get_sqe_mask(sq), true); + jfs_db.pi = cpu_to_le16(sq->pi); + writel(*(u32 *)&jfs_db, sq->db_addr); + sq->last_pi = sq->pi; +} + +netdev_tx_t unic_start_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + struct netdev_queue *dev_queue; + struct unic_sq *sq; + bool doorbell; + u32 sge_num; + int ret; + + sq = unic_dev->channels.c[skb->queue_mapping].sq; + + if (!unic_dev_ubl_supported(unic_dev) && + skb_put_padto(skb, UNIC_MIN_TX_LEN)) { + unic_sq_stats_inc(sq, pad_err); + goto xmit_drop_pkt; + } + + /* Prefetch the data used later */ + prefetch(skb->data); + + sge_num = unic_tx_need_sge_num(skb); + ret = unic_maybe_stop_tx(netdev, sq, sge_num); + if (unlikely(ret < 0)) { + if (ret == -EBUSY) { + unic_tx_compensate_doorbell(sq); + return NETDEV_TX_BUSY; + } + + unic_err(unic_dev, "unic stop tx, ret = %d.\n", ret); + goto xmit_drop_pkt; + } + +#ifdef CONFIG_UB_UNIC_UBL + if (unlikely(unic_apply_ub_pkt(unic_dev, sq, skb))) + goto xmit_drop_pkt; +#endif + + ret = unic_handle_sqe(skb, sq, sge_num); + if (unlikely(ret)) + goto xmit_drop_pkt; + + dev_queue = netdev_get_tx_queue(netdev, sq->queue_index); + doorbell = __netdev_tx_sent_queue(dev_queue, skb->len, + netdev_xmit_more()); + + unic_tx_doorbell(sq, sge_num, doorbell); + + return NETDEV_TX_OK; + +xmit_drop_pkt: + unic_tx_compensate_doorbell(sq); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; +} + +void unic_clear_sq(struct unic_sq *sq) +{ + unic_reclaim_sq_space(sq, 0, NULL, NULL, true); + sq->start_pi = sq->pi; + sq->check_ci_late = true; +} + +void unic_reset_tx_queue(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + struct netdev_queue *dev_queue; + struct unic_sq *sq; + u32 i; + + if (!unic_dev->channels.c) + return; + + for (i = 0; i < unic_dev->channels.num; i++) { + sq = unic_dev->channels.c[i].sq; + dev_queue = netdev_get_tx_queue(netdev, sq->queue_index); + netdev_tx_reset_queue(dev_queue); + } +} + +void unic_dump_sq_stats(struct net_device *netdev, u32 queue_idx) +{ + struct netdev_queue *queue = netdev_get_tx_queue(netdev, queue_idx); + struct unic_dev *unic_dev = netdev_priv(netdev); + struct unic_sq_stats *sq_stats; + struct unic_channel *channel; + struct unic_sq *sq; + + channel = &unic_dev->channels.c[queue_idx]; + sq = channel->sq; + sq_stats = &sq->stats; + + unic_info(unic_dev, + "tx timeout, queue index: %u, state: %lu\n" + "sq->pi: %u\n" + "sq->ci: %u\n" + "pad_err: %llu\n" + "bytes: %llu\n" + "packets: %llu\n" + "map_err: %llu\n" + "busy: %llu\n" + "more: %llu\n" + "restart_queue: %llu\n" + "over_max_sge_num: %llu\n" + "csum_err: %llu\n" + "ci_mismatch: %llu\n" + "fd_cnt: %llu\n" + "drop_cnt: %llu\n" + "cfg5_drop_cnt: %llu\n", + queue_idx, queue->state, sq->pi, sq->ci, + sq_stats->pad_err, sq_stats->bytes, sq_stats->packets, + sq_stats->map_err, sq_stats->busy, sq_stats->more, + sq_stats->restart_queue, sq_stats->over_max_sge_num, + sq_stats->csum_err, sq_stats->ci_mismatch, sq_stats->fd_cnt, + sq_stats->drop_cnt, sq_stats->cfg5_drop_cnt); +} + +void unic_mask_key_words(void *sqebb) +{ + struct unic_sqe_ctrl_section *ctrl; + struct unic_sqe_sge_section *sge; + u16 i; + + ctrl = (struct unic_sqe_ctrl_section *)sqebb; + sge = (struct unic_sqe_sge_section *)(ctrl + 1); + for (i = 0; i < ctrl->sge_num; i++) { + sge->address = 0; + sge++; + } +} diff --git a/drivers/net/ub/unic/unic_tx.h b/drivers/net/ub/unic/unic_tx.h new file mode 100644 index 0000000000000000000000000000000000000000..ff74c6bfda2fd9db17ef6f9b1d9e2c8b5bbe7ecd --- /dev/null +++ b/drivers/net/ub/unic/unic_tx.h @@ -0,0 +1,305 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_TX_H__ +#define __UNIC_TX_H__ + +#include + +struct unic_dev; + +enum unic_jfs_ctx_mode { + UNIC_JFS, + UNIC_JETTY, +}; + +#define UNIC_RAW_TYPE 0 +#define UNIC_RAW_SQEBB_SIZE 64 + +#define UNIC_SQE_VA0_OFFSET 12 +#define UNIC_SQE_VA0_VALID_BIT GENMASK(19, 0) +#define UNIC_SQE_VA1_OFFSET 32 +#define UNIC_SQE_VA1_VALID_BIT GENMASK(31, 0) +#define UNIC_SQE_TOKEN_ID_L_MASK GENMASK(11, 0) +#define UNIC_SQE_TOKEN_ID_H_OFFSET 12U +#define UNIC_SQE_TOKEN_ID_H_MASK GENMASK(7, 0) +#define UNIC_SQE_SL_BIT GENMASK(3, 0) + +#define UNIC_JFS_DB_BASE_OFFSET 0x1080 +#define UNIC_JFS_DB_4K_OFFSET 0x1000 +#define UNIC_JFS_DB_64K_OFFSET 0x10000 + +#define UNIC_PAGE_SIZE_4K 4096 +#define UNIC_AVAIL_SGMT_OST_INIT 512 + +#define unic_sq_stats_inc(sq, cnt) do { \ + typeof(sq) _sq = (sq); \ + u64_stats_update_begin(&(_sq)->syncp); \ + ((_sq)->stats.cnt)++; \ + u64_stats_update_end(&(_sq)->syncp); \ + } while (0) + +union l3_hdr_info { + struct iphdr *v4; + struct ipv6hdr *v6; + u8 *hdr; +}; + +union l4_hdr_info { + struct tcphdr *tcp; + struct udphdr *udp; + u8 *hdr; +}; + +enum unic_pkt_l4_type { + UNIC_L4_TYPE_UNKNOWN, + UNIC_L4_TYPE_TCP, + UNIC_L4_TYPE_UDP, + UNIC_L4_TYPE_RESERVE +}; + +enum unic_pkt_tun_type { + UNIC_TUN_NONE, + UNIC_TUN_UDP, + UNIC_TUN_NVGRE, + UNIC_TUN_OTHER +}; + +enum unic_jfs_ta_timeout { + UNIC_TIMEOUT_128MS, + UNIC_TIMEOUT_1S, + UNIC_TIMEOUT_8S, + UNIC_TIMEOUT_64S +}; + +enum unic_jfs_state { + UNIC_JFS_STATE_RESET, + UNIC_JFS_STATE_READY, + UNIC_JFS_STATE_ERROR, + UNIC_JFS_STATE_SUSPEND, + UNIC_JFS_STATE_RESERVED +}; + +struct unic_sq_stats { + u64 pad_err; + u64 bytes; + u64 packets; + u64 map_err; + u64 busy; + u64 more; + u64 restart_queue; + u64 over_max_sge_num; + u64 csum_err; + u64 ci_mismatch; + u64 vlan_err; + u64 fd_cnt; + u64 drop_cnt; + u64 cfg5_drop_cnt; +}; + +struct unic_jfs_ctx { + /* DW0 */ + u32 ta_timeout : 2; + u32 rnr_retry_num : 3; + u32 type : 3; + u32 sqe_bb_shift : 4; + u32 sl : 4; + u32 state : 3; + u32 jfs_mode : 1; + u32 sqe_token_id_l : 12; + /* DW1 */ + u32 sqe_token_id_h : 8; + u32 err_mode : 1; + u32 rsv0 : 1; + u32 cmp_odr : 1; + u32 rsv1 : 1; + u32 sqe_base_addr_l : 20; + /* DW2 */ + u32 sqe_base_addr_h; + /* DW3 */ + u32 rsv2; + /* DW4 */ + u32 tx_jfcn : 20; + u32 jfrn : 12; + /* DW5 */ + u32 jfrn1 : 8; + u32 rsv3 : 4; + u32 rx_jfcn : 20; + /* DW6 */ + u32 seid_idx : 10; + u32 pi_type : 1; + u32 rsv4 : 21; + /* DW7 */ + u32 user_data_l; + /* DW8 */ + u32 user_data_h; + /* DW9 */ + u32 sqe_position : 1; + u32 sqe_pld_position : 1; + u32 sqe_pld_tokenid : 20; + u32 rsv5 : 10; + /* DW10 */ + u32 tpn : 24; + u32 rsv6 : 8; + /* DW11 */ + u32 rmt_eid : 20; + u32 rsv7 : 12; + /* DW12 */ + u32 rmt_tokenid : 20; + u32 rsv8 : 12; + /* DW13 - DW15 */ + u32 rsv8_1[3]; + /* DW16 */ + u32 next_send_ssn : 16; + u32 src_order_wqe : 16; + /* DW17 */ + u32 src_order_ssn : 16; + u32 src_order_sgme_cnt : 16; + /* DW18 */ + u32 src_order_sgme_send_cnt : 16; + u32 CI : 16; + /* DW19 */ + u32 wqe_sgmt_send_cnt : 20; + u32 src_order_wqebb_num : 4; + u32 src_order_wqe_vld : 1; + u32 no_wqe_send_cnt : 4; + u32 so_lp_vld : 1; + u32 fence_lp_vld : 1; + u32 strong_fence_lp_vld : 1; + /* DW20 */ + u32 PI : 16; + u32 sq_db_doing : 1; + u32 ost_rce_credit : 15; + /* DW21 */ + u32 sq_db_retrying : 1; + u32 wmtp_rsv0 : 31; + /* DW22 */ + u32 wait_ack_timeout : 1; + u32 wait_rnr_timeout : 1; + u32 cqe_ie : 1; + u32 cqe_sz : 1; + u32 wml_rsv0 : 28; + /* DW23 */ + u32 wml_rsv1 : 32; + /* DW24 */ + u32 next_rcv_ssn : 16; + u32 next_cpl_bb_idx : 16; + /* DW25 */ + u32 next_cpl_sgmt_num : 20; + u32 we_rsv0 : 12; + /* DW26 */ + u32 next_cpl_bb_num : 4; + u32 next_cpl_cqe_en : 1; + u32 next_cpl_info_vld : 1; + u32 rpting_cqe : 1; + u32 not_rpt_cqe : 1; + u32 flush_ssn : 16; + u32 flush_ssn_vld : 1; + u32 flush_vld : 1; + u32 flush_cqe_done : 1; + u32 we_rsv1 : 5; + /* DW27 */ + u32 rcved_cont_ssn_num : 20; + u32 we_rsv2 : 12; + /* DW28 */ + u32 sq_timer; + /* DW29 */ + u32 rnr_cnt : 3; + u32 abt_ssn : 16; + u32 abt_ssn_vld : 1; + u32 taack_timeout_flag : 1; + u32 we_rsv3 : 9; + u32 err_type_l : 2; + /* DW30 */ + u32 err_type_h : 7; + u32 sq_flush_ssn : 16; + u32 we_rsv4 : 9; + /* DW31 */ + u32 avail_sgmt_ost : 10; + u32 read_op_cnt : 10; + u32 we_rsv5 : 12; +}; + +struct unic_sqe_ctrl_section { + __le16 sqe_bd_idx; + __le16 l3_type : 2; + __le16 l4_type : 2; + __le16 l3_csum : 1; + __le16 l4_csum : 1; + __le16 ol3_type : 2; + __le16 ol4_csum : 1; + __le16 tun_type : 4; + __le16 tsyn : 1; + __le16 tso : 1; + __le16 owner : 1; + + u8 l2hdr_len; + u8 l3hdr_len; + u8 l4hdr_len; + u8 sge_num : 5; + u8 rsv1 : 2; + u8 dip_type : 1; + + u8 ol2hdr_len; + u8 ol3hdr_len; + u8 ol4hdr_len; + u8 rsv2; + + __le32 mss : 14; + __le32 rsv3 : 18; + + __le32 dip0; + __le32 dip1; + __le32 dip2; + __le32 dip3; +}; + +struct unic_sqe_sge_section { + __le32 length; + __le32 token_id : 20; + __le32 rsv : 12; + __le64 address; +}; + +struct unic_sqebb { + u64 data[8]; +}; + +struct unic_jfs_db { + __le16 pi; + __le16 rsv; +}; + +struct unic_sq { + void __iomem *db_addr; + struct device *parent_dev; + struct unic_sq_stats stats; + struct u64_stats_sync syncp; + struct unic_jfs_ctx jfs_ctx; + struct unic_sqebb *sqebb; + dma_addr_t sqebb_dma_addr; + struct sk_buff **skbs; + struct unic_cq *cq; + u16 pi; /* the start of next to product */ + u16 ci; /* the start of next to consume */ + u16 last_pi; + u16 start_pi; + bool check_ci_late; + u16 queue_index; + struct net_device *netdev; +}; + +void unic_poll_tx(struct unic_sq *sq, int budget); +int unic_create_sq(struct unic_dev *unic_dev, u32 idx); +void unic_destroy_sq(struct unic_dev *unic_dev, u32 num); + +netdev_tx_t unic_start_xmit(struct sk_buff *skb, struct net_device *netdev); +void unic_clear_sq(struct unic_sq *sq); +void unic_reset_tx_queue(struct net_device *netdev); +void unic_dump_sq_stats(struct net_device *netdev, u32 queue_idx); +void unic_mask_key_words(void *sqebb); + +#endif diff --git a/drivers/net/ub/unic/unic_txrx.c b/drivers/net/ub/unic/unic_txrx.c new file mode 100644 index 0000000000000000000000000000000000000000..41017608fc513ee7220529acbbdb14efe92d1104 --- /dev/null +++ b/drivers/net/ub/unic/unic_txrx.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#define dev_fmt(fmt) "unic: (pid %d) " fmt, current->pid + +#include + +#include "unic_dev.h" +#include "unic_txrx.h" + +static u16 unic_get_cqe_period(u16 cqe_period) +{ + u16 period[] = { + UNIC_CQE_PERIOD_0, + UNIC_CQE_PERIOD_4, + UNIC_CQE_PERIOD_16, + UNIC_CQE_PERIOD_64, + UNIC_CQE_PERIOD_256, + UNIC_CQE_PERIOD_1024, + UNIC_CQE_PERIOD_4096, + UNIC_CQE_PERIOD_16384 + }; + u16 i; + + for (i = 0; i < ARRAY_SIZE(period); ++i) { + if (cqe_period == period[i]) + return i; + } + + return 0; +} + +static void unic_init_jfc_ctx(struct unic_cq *cq, u8 jfc_shift, u32 tid, + u16 cqe_coal_cnt, u16 cqe_coal_period) +{ + struct unic_jfc_ctx *ctx = &cq->jfc_ctx; + + ctx->state = UNIC_JFC_STATE_VALID; + ctx->arm_st = UNIC_CTX_ALWAY_ARMED; + ctx->shift = jfc_shift - UNIC_CTX_SHIFT_BASE; + ctx->record_db_en = 0; + ctx->jfc_type = UNIC_RAW_JFC_TYPE; + ctx->inline_en = UNIC_NO_INLINE_DATA; + ctx->cqe_base_addr_l = (cq->cqe_dma_addr >> UNIC_CQE_VA_L_OFFSET) & + UNIC_CQE_VA_L_VALID_BIT; + ctx->cqe_base_addr_h = (cq->cqe_dma_addr >> UNIC_CQE_VA_H_OFFSET) & + UNIC_CQE_VA_H_VALID_BIT; + ctx->queue_token_id = tid; + ctx->cq_cnt_mode = UNIC_CQE_CNT_MODE_BY_COUNT; + ctx->ceqn = 0; + ctx->cqe_coalesce_cnt = cqe_coal_cnt; + ctx->cqe_coalesce_period = unic_get_cqe_period(cqe_coal_period); +} + +static int unic_cq_alloc_resource(struct auxiliary_device *adev, + struct unic_cq *cq, u32 cqe_depth) +{ + u32 size = cqe_depth * unic_get_cqe_size(); + + cq->cqe = dma_alloc_coherent(adev->dev.parent, size, + &cq->cqe_dma_addr, GFP_KERNEL); + if (!cq->cqe) { + dev_err(adev->dev.parent, "failed to dma alloc unic cqe.\n"); + return -ENOMEM; + } + + return 0; +} + +static void unic_cq_free_resource(struct auxiliary_device *adev, + struct unic_cq *cq, u32 cqe_depth) +{ + u32 size = cqe_depth * unic_get_cqe_size(); + union unic_cqe *cqe = cq->cqe; + + dma_free_coherent(adev->dev.parent, size, cqe, cq->cqe_dma_addr); +} + +static int unic_mbx_create_jfc_context(struct auxiliary_device *adev, + struct unic_cq *cq, u32 jfcn) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mailbox for create jfc context, jfcn = %u.\n", + jfcn); + return -ENOMEM; + } + + memcpy(mailbox->buf, &cq->jfc_ctx, sizeof(struct unic_jfc_ctx)); + ubase_fill_mbx_attr(&attr, jfcn, UBASE_MB_CREATE_JFC_CONTEXT, 0); + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to post create jfc ctx mbx, jfcn = %u, ret = %d\n", + jfcn, ret); + + ubase_free_cmd_mailbox(adev, mailbox); + return ret; +} + +static int unic_post_destroy_jfc_ctx(struct auxiliary_device *adev, u32 jfcn) +{ + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + int ret; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mbx for post destroy jfcn(%u) ctx.\n", + jfcn); + return -ENOMEM; + } + + ubase_fill_mbx_attr(&attr, jfcn, UBASE_MB_DESTROY_JFC_CONTEXT, 0); + + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) + dev_err(adev->dev.parent, + "failed to post destroy jfcn(%u) ctx mbx, ret=%d.\n", + jfcn, ret); + + ubase_free_cmd_mailbox(adev, mailbox); + return ret; +} + +#if defined(UNIC_FPGA_COMPILE) || defined(UNIC_EVB_COMPILE) +static void unic_jfc_wait_flush_done(struct unic_dev *unic_dev, u32 jfcn) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_cmd_mailbox *mailbox; + struct ubase_mbx_attr attr = {0}; + struct unic_jfc_ctx *ctx; + int ret; + + if (test_bit(UNIC_STATE_RESETTING, &unic_dev->state)) + return; + + mailbox = ubase_alloc_cmd_mailbox(adev); + if (IS_ERR_OR_NULL(mailbox)) { + dev_err(adev->dev.parent, + "failed to alloc mbx for jfc(%u) flush.\n", jfcn); + return; + } + + ubase_fill_mbx_attr(&attr, jfcn, UBASE_MB_QUERY_JFC_CONTEXT, 0); + + ret = ubase_hw_upgrade_ctx_ex(adev, &attr, mailbox); + if (ret) { + dev_err(adev->dev.parent, + "failed to wait jfcn(%u) flush, ret=%d\n", jfcn, ret); + goto out; + } + + ctx = (struct unic_jfc_ctx *)mailbox->buf; + if (ctx->wr_cqe_idx == ctx->pi) + goto out; + + dev_err(adev->dev.parent, "wait jfcn(%u) flush done timeout.\n", jfcn); +out: + ubase_free_cmd_mailbox(adev, mailbox); +} + +static void unic_jfc_wait_flush(struct unic_dev *unic_dev, + enum unic_cq_type type, u32 num, + unsigned long des_bitmap) +{ +#define UNIC_WAIT_FLUSH_JFC_TIME 10 + + struct unic_channel *c; + struct unic_cq **cq; + u32 i; + + /* Wait for the chip to complete the flush process through sleep */ + msleep(UNIC_WAIT_FLUSH_JFC_TIME); + + for (i = 0; i < num; i++) { + if (!test_bit(i, &des_bitmap)) + continue; + + c = &unic_dev->channels.c[i]; + cq = type == UNIC_CQ_RQ ? &c->rq_cq : &c->sq_cq; + unic_jfc_wait_flush_done(unic_dev, (*cq)->jfcn); + } +} +#endif + +static void unic_destroy_multi_jfc_context(struct unic_dev *unic_dev, + enum unic_cq_type type, u32 num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + unsigned long des_bitmap = 0; + struct unic_channel *c; + struct unic_cq **cq; + int ret; + u32 i; + + /* Since jfc wait flush is a post-condition, + * there is no need to query if jfc destroy fails here. + * Other jfs/jfr query after set error, and query is a precondition. + */ + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + cq = type == UNIC_CQ_RQ ? &c->rq_cq : &c->sq_cq; + ret = unic_post_destroy_jfc_ctx(adev, (*cq)->jfcn); + if (!ret) + set_bit(i, &des_bitmap); + } + +#if defined(UNIC_FPGA_COMPILE) || defined(UNIC_EVB_COMPILE) + unic_jfc_wait_flush(unic_dev, type, num, des_bitmap); +#endif +} + +int unic_create_cq(struct unic_dev *unic_dev, u32 idx, enum unic_cq_type type) +{ + struct unic_channel *channel = &unic_dev->channels.c[idx]; + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct ubase_resource_space *mem_base = ubase_get_mem_base(adev); + u16 channels_num = unic_dev->channels.num; + struct unic_coalesce *coal; + struct unic_cq *cq; + u32 cqe_depth; + u8 jfc_shift; + int ret; + + if (!mem_base) + return -ENODATA; + + cq = devm_kzalloc(&adev->dev, sizeof(*cq), GFP_KERNEL); + if (!cq) + return -ENOMEM; + + if (type == UNIC_CQ_RQ) { + cqe_depth = unic_dev->channels.rq_cqe_depth; + cq->jfcn = idx + channels_num; + coal = &unic_dev->channels.unic_coal.rx_coal; + } else { + cqe_depth = unic_dev->channels.sq_cqe_depth; + cq->jfcn = idx; + coal = &unic_dev->channels.unic_coal.tx_coal; + } + + ret = unic_cq_alloc_resource(adev, cq, cqe_depth); + if (ret) + goto err_alloc_res; + + channels_num = unic_dev->channels.num; + jfc_shift = ilog2(roundup_pow_of_two(cqe_depth)); + unic_init_jfc_ctx(cq, jfc_shift, unic_dev->tid, + coal->int_ql, coal->int_gl); + + cq->db_addr = mem_base->addr + UNIC_JFC_DB_OFFSET; + + ret = unic_mbx_create_jfc_context(adev, cq, cq->jfcn); + if (ret) + goto err_fill_send_cq_mbx; + + if (type) + channel->rq_cq = cq; + else + channel->sq_cq = cq; + + return 0; + +err_fill_send_cq_mbx: + unic_cq_free_resource(adev, cq, cqe_depth); +err_alloc_res: + devm_kfree(&adev->dev, cq); + return ret; +} + +static void unic_multi_cq_res_destroy(struct unic_dev *unic_dev, + enum unic_cq_type type, u32 num) +{ + struct auxiliary_device *adev = unic_dev->comdev.adev; + struct unic_channel *c; + struct unic_cq **cq; + u32 cqe_depth, i; + + cqe_depth = type == UNIC_CQ_RQ ? unic_dev->channels.rq_cqe_depth : + unic_dev->channels.sq_cqe_depth; + + for (i = 0; i < num; i++) { + c = &unic_dev->channels.c[i]; + cq = type == UNIC_CQ_RQ ? &c->rq_cq : &c->sq_cq; + unic_cq_free_resource(adev, *cq, cqe_depth); + devm_kfree(&adev->dev, *cq); + *cq = NULL; + } +} + +void unic_destroy_cq(struct unic_dev *unic_dev, u32 num, enum unic_cq_type type) +{ + if (!num) + return; + + /* The hardware does not access the configured memory after the reset, + * directly destroy the cq. + */ + if (test_bit(UNIC_STATE_RESETTING, &unic_dev->state)) + goto cq_res_destroy; + + unic_destroy_multi_jfc_context(unic_dev, type, num); + +cq_res_destroy: + unic_multi_cq_res_destroy(unic_dev, type, num); +} + +int unic_napi_poll(struct napi_struct *napi, int budget) +{ + struct unic_channel *c = container_of(napi, struct unic_channel, napi); + struct net_device *netdev = c->sq->netdev; + struct unic_dev *unic_dev = netdev_priv(netdev); + int work_done; + + if (unlikely(test_bit(UNIC_STATE_DOWN, &unic_dev->state))) { + napi_complete(napi); + return 0; + } + + unic_poll_tx(c->sq, budget); + + work_done = unic_poll_rx(c, budget, unic_send_skb_to_stack); + if (work_done >= budget) + return budget; + + napi_complete_done(napi, work_done); + + return work_done; +} + +void unic_cq_doorbell(struct unic_cq *cq, u32 last_ci) +{ + struct unic_jfc_db jfc_db = {0}; + + if (unlikely(cq->ci == last_ci)) + return; + + jfc_db.ci = cq->ci; + jfc_db.jfc_num = cq->jfcn; + + writeq(*(u64 *)&jfc_db, cq->db_addr); +} + +void unic_clear_all_queue(struct net_device *netdev) +{ + struct unic_dev *unic_dev = netdev_priv(netdev); + struct unic_channel *c; + u32 i; + + mutex_lock(&unic_dev->channels.mutex); + + if (!unic_dev->channels.c) + goto out; + + for (i = 0; i < unic_dev->channels.num; i++) { + c = &unic_dev->channels.c[i]; + unic_clear_sq(c->sq); + unic_clear_rq(c->rq); + } +out: + mutex_unlock(&unic_dev->channels.mutex); +} diff --git a/drivers/net/ub/unic/unic_txrx.h b/drivers/net/ub/unic/unic_txrx.h new file mode 100644 index 0000000000000000000000000000000000000000..b52354c8989f7fc084cc5d35d284161524cdf2ab --- /dev/null +++ b/drivers/net/ub/unic/unic_txrx.h @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UNIC_TXRX_H__ +#define __UNIC_TXRX_H__ + +#include + +struct unic_dev; + +#define UNIC_CTX_SHIFT_BASE 6 + +#define UNIC_CQE_VA_L_OFFSET 12 +#define UNIC_CQE_VA_L_VALID_BIT GENMASK(19, 0) +#define UNIC_CQE_VA_H_OFFSET 32 +#define UNIC_CQE_VA_H_VALID_BIT GENMASK(31, 0) + +#define UNIC_CEQN_VALID_BIT GENMASK(7, 0) + +#define UNIC_JFC_DB_OFFSET 0x40 + +enum unic_armed_jfc { + UNIC_CTX_NO_ARMED, + UNIC_CTX_ALWAY_ARMED, + UNIC_CTX_REG_NEXT_CEQE, + UNIC_CTX_REG_NEXT_SOLICITED_CEQE, +}; + +enum unic_jfc_state { + UNIC_JFC_STATE_INVALID, + UNIC_JFC_STATE_VALID, + UNIC_JFC_STATE_ERROR, +}; + +enum unic_jfc_type { + UNIC_NORMAL_JFC_TYPE, + UNIC_RAW_JFC_TYPE, +}; + +enum unic_jfc_inline { + UNIC_NO_INLINE_DATA, + UNIC_INLINE_DATA, +}; + +enum unic_cqe_size { + UNIC_64_JFC_CQE_SIZE, + UNIC_128_JFC_CQE_SIZE, +}; + +enum unic_cq_cnt_mode { + UNIC_CQE_CNT_MODE_BY_COUNT, + UNIC_CQE_CNT_MODE_BY_CI_PI_GAP, +}; + +enum unic_record_db { + UNIC_NO_RECORD_EN, + UNIC_RECORD_EN, +}; + +enum unic_cq_type { + UNIC_CQ_SQ = 0, + UNIC_CQ_RQ, +}; + +enum unic_jfc_db_type { + UNIC_JFC_CQ_DB, + UNIC_JFC_CQ_ARM_DB, +}; + +enum unic_jfc_db_notify { + UNIC_JFC_NOTIFY_UNSOLICITED, + UNIC_JFC_NOTIFY_SOLICITED, +}; + +enum unic_pkt_l3_type { + /* 0 ~ 3 is also used by the raw sqe L3T */ + UNIC_L3_TYPE_IPV4, + UNIC_L3_TYPE_IPV6, + UNIC_L3_TYPE_NON_IP, + + UNIC_L3_TYPE_ARP, + UNIC_L3_TYPE_RARP, + UNIC_L3_TYPE_LLDP, + UNIC_L3_TYPE_STP, + UNIC_L3_TYPE_MAC_PAUSE, + UNIC_L3_TYPE_PFC_PAUSE, + UNIC_L3_TYPE_CNM, + UNIC_L3_TYPE_PTP, + + UNIC_L3_TYPE_RESV +}; + +struct unic_jfc_ctx { + /* DW0 */ + u32 state : 2; + u32 arm_st : 2; + u32 shift : 4; + u32 cqe_size : 1; + u32 record_db_en : 1; + u32 jfc_type : 1; + u32 inline_en : 1; + u32 cqe_base_addr_l : 20; + /* DW1 */ + u32 cqe_base_addr_h; + /* DW2 */ + u32 queue_token_id : 20; + u32 cq_cnt_mode : 1; + u32 rsv0 : 3; + u32 ceqn : 8; + /* DW3 */ + u32 ad : 24; + u32 rsv1 : 8; + /* DW4 */ + u32 pi : 22; + u32 cqe_coalesce_cnt : 10; + /* DW5 */ + u32 ci : 22; + u32 cqe_coalesce_period : 3; + u32 rsv2 : 7; + /* DW6 */ + u32 record_db_addr_l; + /* DW7 */ + u32 record_db_addr_h : 26; + u32 rsv3 : 6; + /* DW8 */ + u32 queue_position : 1; + u32 push_cqe_en : 1; + u32 token_en : 1; + u32 rsv4 : 9; + u32 tpn : 20; + /* DW9 ~ DW12 */ + u32 rmt_eid[4]; + /* DW13 */ + u32 seid_idx : 10; + u32 rmt_token_id : 20; + u32 rsv5 : 2; + /* DW14 */ + u32 remote_token_value; + /* DW15 */ + u32 int_vector : 16; + u32 stars_en : 1; + u32 rsv6 : 15; + /* DW16 */ + u32 poll : 1; + u32 cqe_report_timer : 24; + u32 se : 1; + u32 arm_sn : 2; + u32 rsv7 : 4; + /* DW17 */ + u32 se_cqe_idx : 24; + u32 rsv8 : 8; + /* DW18 */ + u32 wr_cqe_idx : 22; + u32 rsv9 : 10; + /* DW19 */ + u32 cqe_cnt : 24; + u32 rsv10 : 8; + /* DW20 ~ DW31 */ + u32 rsv12[12]; +}; + +union unic_cqe { + struct { + /* DW0 */ + u8 resv0 : 2; + u8 owner : 1; + u8 resv1 : 4; + u8 fd : 1; + u8 resv2; + u8 sub_status; + u8 status; + /* DW1 */ + u16 raw_ci; + u16 lcl_jfsn_low; + /* DW2 */ + u32 lcl_jfsn_high : 4; + u32 resv3 : 28; + /* DW3 ~ DW15 */ + u32 resv4[13]; + } tx; + + struct { + /* DW0 */ + u8 owner : 1; + u8 doi : 1; + u8 ol3_err : 1; + u8 ol4_err : 1; + u8 ts_vld : 1; + u8 udp0 : 1; + u8 trunc : 1; + u8 resv1 : 1; + u8 crcp : 1; + u8 l3l4p : 1; + u8 hoi : 1; + u8 l2_type : 2; + u8 l2_err : 1; + u8 l3_err : 1; + u8 l4_err : 1; + u8 ptype; + u8 ol2_type : 2; + u8 resv2 : 6; + /* DW1 */ + u16 start_rqe_idx; + u16 packet_len; + /* DW2 */ + u32 lcl_jfrn : 20; + u32 resv3 : 12; + /* DW3 */ + u32 rss_hash; + /* DW4 ~ DW5 */ + u64 timestamp; + /* DW6 */ + u16 unknown_pkt_l2_checksum; + u16 resv4; + /* DW7 ~ DW15 */ + u32 resv5[9]; + } rx; +}; + +struct unic_jfc_db { + __le32 ci : 24; + __le32 notify : 1; + __le32 cmd_ssn : 2; + __le32 type : 1; + __le32 rsv0 : 4; + + __le32 jfc_num : 20; + __le32 rsv1 : 12; +}; + +struct unic_cq { + void __iomem *db_addr; + union unic_cqe *cqe; + dma_addr_t cqe_dma_addr; + struct unic_jfc_ctx jfc_ctx; + u32 jfcn; + u32 ci; /* the start of next to consume */ + u64 event_cnt; +}; + +static inline u8 unic_get_cqe_size(void) +{ + return sizeof(union unic_cqe); +} + +static inline bool unic_cqe_owner_is_soft(u8 jfc_shift, u32 ci, u8 owner) +{ + return owner != ((ci >> jfc_shift) & 1); +} + +int unic_create_cq(struct unic_dev *unic_dev, u32 idx, enum unic_cq_type type); +void unic_destroy_cq(struct unic_dev *unic_dev, u32 num, enum unic_cq_type); +int unic_napi_poll(struct napi_struct *napi, int budget); +void unic_cq_doorbell(struct unic_cq *cq, u32 last_ci); +void unic_clear_all_queue(struct net_device *netdev); + +#endif diff --git a/drivers/perf/hisilicon/Kconfig b/drivers/perf/hisilicon/Kconfig index 171bfc1b6bc27dc195c92e2ccc31665effce74c6..b10c18dc1de743b0819e81cd743f4a707569010a 100644 --- a/drivers/perf/hisilicon/Kconfig +++ b/drivers/perf/hisilicon/Kconfig @@ -24,3 +24,13 @@ config HNS3_PMU devices. Adds the HNS3 PMU into perf events system for monitoring latency, bandwidth etc. + +config UB_UMMU_PMU + tristate "HiSilicon UMMU PMU Support" + depends on ARM64 + default n + help + Provide support for hisilicon UMMU performance monitoring unit (PMU) + devices. + Adds the UMMU PMU into perf events system for cache hit rate, latency, + package rate etc. diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile index 186be3d02238b1989ca1edeff51c941c1d9fc3ca..8cd87883cfba8450a0b41eb8440e45426cc28bc2 100644 --- a/drivers/perf/hisilicon/Makefile +++ b/drivers/perf/hisilicon/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o \ obj-$(CONFIG_HISI_PCIE_PMU) += hisi_pcie_pmu.o obj-$(CONFIG_HNS3_PMU) += hns3_pmu.o +obj-$(CONFIG_UB_UMMU_PMU) += ummu-pmu.o +ummu-pmu-y := ummu_pmu.o diff --git a/drivers/perf/hisilicon/ummu_pmu.c b/drivers/perf/hisilicon/ummu_pmu.c new file mode 100644 index 0000000000000000000000000000000000000000..04c05921088e676a6ff9a795b7656becc72165cd --- /dev/null +++ b/drivers/perf/hisilicon/ummu_pmu.c @@ -0,0 +1,1187 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * Description: This driver adds support for perf events to use the Performance + * Monitor Counter Groups (PMCG) associated with an UMMU node to monitor that node. + */ + +#include +#include +#include +#include +#include + +#define UMMU_PMCG_MAX_COUNTERS 8 + +struct ummu_pmu { + struct pmu pmu; + struct hlist_node node; + struct perf_event *events[UMMU_PMCG_MAX_COUNTERS]; + DECLARE_BITMAP(used_counters, UMMU_PMCG_MAX_COUNTERS); + uint32_t irq; + uint32_t on_cpu; + uint32_t num_counters; + struct device *dev; + void __iomem *reg_base; +}; + +/* registers offset address */ +#define UMMU_PMCG_GLB_CTRL 0x0 +#define UMMU_PMCG_GLB_CTRL_COMMON_CLR BIT(4) +#define UMMU_PMCG_GLB_CTRL_COMMON_ENABLE BIT(0) +#define UMMU_PMCG_OVF_USI_ADDR0 0x08 +#define UMMU_PMCG_OVF_USI_ADDR1 0x0C +#define UMMU_PMCG_OVF_USI_ADDR_MASK GENMASK_ULL(51, 2) +#define UMMU_PMCG_OVF_USI_DATA 0x10 +#define UMMU_PMCG_OVF_USI_ATTR 0x14 + +#define UMMU_PMCG_INT_EN 0x18 +#define UMMU_PMCG_INT_OVF_EN BIT(0) +#define UMMU_PMCG_USI_MPAM 0x1C +#define UMMU_PMCG_USI_MPAM_NS BIT(24) +#define UMMU_PMCG_USI_MPAM_PMG GENMASK(23, 16) +#define UMMU_PMCG_USI_MPAM_PARTID GENMASK(15, 0) +#define UMMU_PMCG_USI_MPAM_PMG_MAX 0x03 +#define UMMU_PMCG_USI_MPAM_PARTID_MAX 0x0FF +#define UMMU_PMCG_USI_IDX 0x20 +#define UMMU_PMCG_ICG_EN 0x24 +#define UMMU_PMCG_ICG_EN_ICG BIT(0) + +#define UMMU_PMCG_COM_CTRL(n) (0x0 + (n)*0x40) +#define UMMU_PMCG_COM_CTRL_EVENT_CLR BIT(24) +#define UMMU_PMCG_COM_CTRL_EVENT_EN BIT(16) +#define UMMU_PMCG_COM_CTRL_EVENT_CODE GENMASK(7, 0) +#define UMMU_PMCG_OVF_INT_STS(n) (0x08 + (n)*0x40) +#define UMMU_PMCG_CNT1_OVERFLOW_INT_STS BIT(1) +#define UMMU_PMCG_CNT0_OVERFLOW_INT_STS BIT(0) +#define UMMU_PMCG_OVF_INT_MSK(n) (0x0C + (n)*0x40) +#define UMMU_PMCG_CNT1_OVERFLOW_INT_MSK BIT(1) +#define UMMU_PMCG_CNT0_OVERFLOW_INT_MSK BIT(0) +#define UMMU_PMCG_FILT_COND_L(n) (0x10 + (n)*0x40) +#define UMMU_PMCG_FILT_COND_L_MASTER_ID GENMASK(31, 20) +#define UMMU_PMCG_FILT_COND_L_TOKEN_ID GENMASK(19, 0) +#define UMMU_PMCG_FILT_COND_H(n) (0x14 + (n)*0x40) +#define UMMU_PMCG_FILT_COND_H_USER_DEF_ID GENMASK(31, 16) +#define UMMU_PMCG_FILT_COND_H_TECT_TAG GENMASK(15, 0) +#define UMMU_PMCG_FILT_MSK_L(n) (0x18 + (n)*0x40) +#define UMMU_PMCG_FILT_MSK_L_MASTER_ID GENMASK(31, 20) +#define UMMU_PMCG_FILT_MSK_L_TOKEN_ID GENMASK(19, 0) +#define UMMU_PMCG_FILT_MSK_H(n) (0x1C + (n)*0x40) +#define UMMU_PMCG_FILT_MSK_H_USER_DEF_ID GENMASK(31, 16) +#define UMMU_PMCG_FILT_MSK_H_TECT_TAG GENMASK(15, 0) +#define UMMU_PMCG_COM_CNT0_L(n) (0x20 + (n)*0x40) +#define UMMU_PMCG_COM_CNT0_H(n) (0x24 + (n)*0x40) +#define UMMU_PMCG_COM_CNT1_L(n) (0x28 + (n)*0x40) +#define UMMU_PMCG_COM_CNT1_H(n) (0x2C + (n)*0x40) + +/* define support event numbers. */ +#define UMMU_TCU_PPTW_REQ_NUM 0x00 +#define UMMU_TCU_CNTX_CACHE_MISS_NUM 0x01 +#define UMMU_TBU_TLB_CACHE_HIT_RATE 0x20 +#define UMMU_TBU_PLB_CACHE_HIT_RATE 0x21 +#define UMMU_TCU_TPTW_CACHE_HIT_RATE 0x22 +#define UMMU_TCU_PPTW_CACHE_HIT_RATE 0x23 +#define UMMU_REQ_RATE 0x4B +#define UMMU_RSP_RATE 0x4E +#define UMMU_REQ_AVERAGE_LATENCY 0x6B +#define UMMU_KV_TABLE_RD_AVERAGE_LATENCY 0x6F +#define UMMU_SWIF_CMD_SEND_NUM 0x04 +#define UMMU_SWIF_DVM_SYNC_LATENCY 0x64 +#define UMMU_SWIF_KCMD_S_SYNC_LATENCY 0x65 +#define UMMU_SWIF_KCMD_NS_SYNC_LATENCY 0x66 +#define UMMU_SWIF_UCMD_SYNC_LATENCY 0x67 +#define UMMU_UBIF_KV_CACHE_HIT_RATE 0x2F +#define UMMU_TCU_GPC_CACHE_HIT_RATE 0x2A +#define UMMU_TCU_TPTW_REQ_LATENCY 0x68 +#define UMMU_TCU_PPTW_REQ_LATENCY 0x69 +#define UMMU_TCU_GPC_REQ_LATENCY 0x6A +#define UMMU_TBU_PTW_PACK_RATE 0x4C +#define UMMU_TBU_PPTW_PACK_RATE 0x4D +#define UMMU_TBU_PTW_LATENCY 0x6C +#define UMMU_TBU_PPTW_LATENCY 0x6D +#define UMMU_TBU_RAB_BUF_USE_RATE 0x8E +#define UMMU_SWIF_KCMD_GPC_SYNC_LATENCY 0x70 +#define UMMU_SWIF_KCMD_REALM_SYNC_LATENCY 0x71 + +#define UMMU_PMCG_CFGR_SIZE 32 +#define UMMU_EVTYPE_SHIFT 5 +#define UMMU_PMCG_MASTER_ID_SHIFT 20 +#define UMMU_PMCG_USER_DEF_ID_SHIFT 16 +#define UMMU_PMCG_MASTER_ID_MSK_SHIFT 20 +#define UMMU_PMCG_USER_DEF_ID_MSK_SHIFT 16 +#define UMMU_PMCG_PA_SHIFT 12 +#define UMMU_USI_MEMATTR_DEVICE_nGnRE 0x01 +#define UMMU_COUNTER_LEN 31 + +static int g_partid; +static int g_pmg; +#define NUMBER_BASE_DECIMAL 10 +#define UMMU_PMU_MULTIPLES_RATE 100 + +#define UMMU_PMU_DRV_NAME "ummu_pmu" + +#define to_ummu_pmu(p) (container_of(p, struct ummu_pmu, pmu)) + +#define UMMU_PMU_EVENT_ATTR_EXTRACTOR(_name, _config, _start, _end) \ + static inline uint32_t get_##_name(struct perf_event *event) \ + { \ + return FIELD_GET(GENMASK_ULL(_end, _start), \ + event->attr._config); \ + } + +UMMU_PMU_EVENT_ATTR_EXTRACTOR(event, config, 0, 7); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(token_id, config1, 0, 19); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(master_id, config1, 20, 31); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(tect_tag, config1, 32, 47); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(user_define_id, config1, 48, 63); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(token_id_flt_msk, config2, 0, 19); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(master_id_flt_msk, config2, 20, 31); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(tect_tag_flt_msk, config2, 32, 47); +UMMU_PMU_EVENT_ATTR_EXTRACTOR(user_define_id_flt_msk, config2, 48, 63); + +enum ummu_pmu_event_type { + UMMU_PMU_EVENT_TYPE_NUM = 0x00, + UMMU_PMU_EVENT_TYPE_HIT_RATE = 0x01, + UMMU_PMU_EVENT_TYPE_FREQ = 0x02, + UMMU_PMU_EVENT_TYPE_AVER_LATENCY = 0x03, + UMMU_PMU_EVENT_TYPE_BUF_OCCUPY_RATE = 0x04, +}; + +static atomic_t ummu_pmu_index = ATOMIC_INIT(0); +static int ummu_pmu_cpuhp_state; + +/* events attributes */ +static ssize_t ummu_pmu_events_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct perf_pmu_events_attr *ummu_pmu_attr; + + ummu_pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr); + + return sysfs_emit(buf, "event=0x%08llx\n", ummu_pmu_attr->id); +} + +#define UMMU_EVENT_ATTR(name, config) \ + PMU_EVENT_ATTR_ID(name, ummu_pmu_events_sysfs_show, config) + +static struct attribute *ummu_pmu_events_attrs[] = { + UMMU_EVENT_ATTR(tcu_pptw_req_num, UMMU_TCU_PPTW_REQ_NUM), + UMMU_EVENT_ATTR(tcu_cntx_cache_miss_num, UMMU_TCU_CNTX_CACHE_MISS_NUM), + UMMU_EVENT_ATTR(tbu_tlb_cache_hit_rate, UMMU_TBU_TLB_CACHE_HIT_RATE), + UMMU_EVENT_ATTR(tbu_plb_cache_hit_rate, UMMU_TBU_PLB_CACHE_HIT_RATE), + UMMU_EVENT_ATTR(tcu_tptw_cache_hit_rate, UMMU_TCU_TPTW_CACHE_HIT_RATE), + UMMU_EVENT_ATTR(tcu_pptw_cache_hit_rate, UMMU_TCU_PPTW_CACHE_HIT_RATE), + UMMU_EVENT_ATTR(ummu_req_rate, UMMU_REQ_RATE), + UMMU_EVENT_ATTR(ummu_rsp_rate, UMMU_RSP_RATE), + UMMU_EVENT_ATTR(ummu_req_average_latency, UMMU_REQ_AVERAGE_LATENCY), + UMMU_EVENT_ATTR(kv_table_rd_average_latency, UMMU_KV_TABLE_RD_AVERAGE_LATENCY), + UMMU_EVENT_ATTR(swif_cmd_send_num, UMMU_SWIF_CMD_SEND_NUM), + UMMU_EVENT_ATTR(swif_dvm_sync_latency, UMMU_SWIF_DVM_SYNC_LATENCY), + UMMU_EVENT_ATTR(swif_kcmd_s_sync_latency, UMMU_SWIF_KCMD_S_SYNC_LATENCY), + UMMU_EVENT_ATTR(swif_kcmd_ns_sync_latency, UMMU_SWIF_KCMD_NS_SYNC_LATENCY), + UMMU_EVENT_ATTR(swif_ucmd_sync_latency, UMMU_SWIF_UCMD_SYNC_LATENCY), + UMMU_EVENT_ATTR(ubif_kv_cache_hit_rate, UMMU_UBIF_KV_CACHE_HIT_RATE), + UMMU_EVENT_ATTR(tcu_gpc_cache_hit_rate, UMMU_TCU_GPC_CACHE_HIT_RATE), + UMMU_EVENT_ATTR(tcu_tptw_req_latency, UMMU_TCU_TPTW_REQ_LATENCY), + UMMU_EVENT_ATTR(tcu_pptw_req_latency, UMMU_TCU_PPTW_REQ_LATENCY), + UMMU_EVENT_ATTR(tcu_gpc_req_latency, UMMU_TCU_GPC_REQ_LATENCY), + UMMU_EVENT_ATTR(tbu_ptw_pack_rate, UMMU_TBU_PTW_PACK_RATE), + UMMU_EVENT_ATTR(tbu_pptw_pack_rate, UMMU_TBU_PPTW_PACK_RATE), + UMMU_EVENT_ATTR(tbu_ptw_latency, UMMU_TBU_PTW_LATENCY), + UMMU_EVENT_ATTR(tbu_pptw_latency, UMMU_TBU_PPTW_LATENCY), + UMMU_EVENT_ATTR(tbu_rab_buf_use_rate, UMMU_TBU_RAB_BUF_USE_RATE), + UMMU_EVENT_ATTR(swif_kcmd_gpc_sync_latency, UMMU_SWIF_KCMD_GPC_SYNC_LATENCY), + UMMU_EVENT_ATTR(swif_kcmd_realm_sync_latency, UMMU_SWIF_KCMD_REALM_SYNC_LATENCY), + NULL +}; + +static const struct attribute_group ummu_pmu_events_attr_group = { + .name = "events", + .attrs = ummu_pmu_events_attrs +}; + +#define PRE_CNT1_MAX_IDX (ARRAY_SIZE(ummu_pmu_events_attrs) - 1) +static uint32_t pre_count1_idx[UMMU_PMCG_MAX_COUNTERS]; +static local64_t pre_count1[ARRAY_SIZE(ummu_pmu_events_attrs)]; +#define PRE_CNT1_ADDR(cnt_idx) (&pre_count1[pre_count1_idx[cnt_idx]]) + +static uint32_t get_local_pre_count_idx(uint32_t event_id) +{ + struct perf_pmu_events_attr *pmu_attr; + uint32_t idx; + + for (idx = 0; idx < ARRAY_SIZE(ummu_pmu_events_attrs) && + ummu_pmu_events_attrs[idx]; idx++) { + pmu_attr = container_of(container_of(ummu_pmu_events_attrs[idx], + struct device_attribute, + attr), + struct perf_pmu_events_attr, attr); + + if (pmu_attr && pmu_attr->id == event_id) + return idx; + } + + return PRE_CNT1_MAX_IDX; +} + +static irqreturn_t ummu_pmu_handle_irq(int irq, void *data) +{ + struct ummu_pmu *ummu_pmu = (struct ummu_pmu *)data; + u32 overflow; + int idx; + + /* + * Find the counter index which overflowed if the bit was set and handle it. + */ + for (idx = 0; idx < UMMU_PMCG_MAX_COUNTERS; idx++) { + overflow = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_OVF_INT_STS(idx + 1)); + /* + * As each counter will restart from 0 when it is overflowed, + * extra processing is no need, just clear interrupt status. + */ + if (overflow) + writel_relaxed(0, ummu_pmu->reg_base + UMMU_PMCG_OVF_INT_STS(idx + 1)); + } + + return IRQ_HANDLED; +} + +static void ummu_pmu_free_msis(void *data) +{ + struct device *dev = (struct device *)data; + + platform_device_msi_free_irqs_all(dev); +} + +static void ummu_pmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg) +{ + struct ummu_pmu *ummu_pmu; + phys_addr_t doorbell; + struct device *dev; + + dev = msi_desc_to_dev(desc); + ummu_pmu = (struct ummu_pmu *)dev_get_drvdata(dev); + /* 32-bit addresses are converted to 64-bit addresses. */ + doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo; + doorbell &= UMMU_PMCG_OVF_USI_ADDR_MASK; + + writeq_relaxed(doorbell, ummu_pmu->reg_base + UMMU_PMCG_OVF_USI_ADDR0); + writel_relaxed(msg->data, ummu_pmu->reg_base + UMMU_PMCG_OVF_USI_DATA); + writel_relaxed(UMMU_USI_MEMATTR_DEVICE_nGnRE, ummu_pmu->reg_base + UMMU_PMCG_OVF_USI_ATTR); +} + +static int ummu_pmu_setup_irq(struct ummu_pmu *ummu_pmu) +{ + unsigned long flags = IRQF_NOBALANCING | IRQF_SHARED | IRQF_NO_THREAD; + struct device *dev = ummu_pmu->dev; + uint32_t val = UMMU_PMCG_INT_OVF_EN; + int ret; + uint32_t irq; + + /* Clear MSI address reg */ + writeq_relaxed(0, ummu_pmu->reg_base + UMMU_PMCG_OVF_USI_ADDR0); + + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_INT_EN); + ret = platform_device_msi_init_and_alloc_irqs(dev, 1, ummu_pmu_write_msi_msg); + if (ret) { + dev_warn(dev, "failed to allocate MSIs, ret = %d\n", ret); + return ret; + } + + irq = msi_get_virq(dev, 0); + if (!irq) { + dev_err(ummu_pmu->dev, "msi get irq failed, irq = %u\n", irq); + platform_device_msi_free_irqs_all(dev); + return -ENXIO; + } + ummu_pmu->irq = irq; + + /* Add callback to free MSIs on teardown */ + ret = devm_add_action_or_reset(dev, ummu_pmu_free_msis, dev); + if (ret) { + dev_err(dev, "failed to add free msis action (%d).\n", ret); + return ret; + } + + return devm_request_irq(ummu_pmu->dev, irq, ummu_pmu_handle_irq, flags, + dev_name(ummu_pmu->dev), ummu_pmu); +} + +static int ummu_pmu_reset(struct ummu_pmu *ummu_pmu) +{ + uint32_t val; + int ret; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); + val |= UMMU_PMCG_GLB_CTRL_COMMON_CLR; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); + + val &= ~UMMU_PMCG_GLB_CTRL_COMMON_CLR; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); + + ummu_pmu->num_counters = UMMU_PMCG_MAX_COUNTERS; + ret = ummu_pmu_setup_irq(ummu_pmu); + if (ret) { + dev_err(ummu_pmu->dev, "error %d setup irq failed!\n", ret); + return ret; + } + + val |= UMMU_PMCG_ICG_EN_ICG; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_ICG_EN); + + /* Pick one CPU to be the preferred one to use */ + ummu_pmu->on_cpu = smp_processor_id(); + ret = cpuhp_state_add_instance_nocalls( + (enum cpuhp_state)ummu_pmu_cpuhp_state, &ummu_pmu->node); + if (ret) + dev_err(ummu_pmu->dev, "error %d registering hotplug!\n", ret); + + return ret; +} + +static void ummu_pmu_enable(struct pmu *pmu) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(pmu); + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); + val |= UMMU_PMCG_GLB_CTRL_COMMON_ENABLE; + val &= ~UMMU_PMCG_GLB_CTRL_COMMON_CLR; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); +} + +static void ummu_pmu_disable(struct pmu *pmu) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(pmu); + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); + val &= ~UMMU_PMCG_GLB_CTRL_COMMON_ENABLE; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_GLB_CTRL); +} + +static void ummu_pmu_counter_get_value(struct ummu_pmu *ummu_pmu, int idx, + uint64_t *cnt0, uint64_t *cnt1) +{ + uint64_t val0_high, val0_low; + uint64_t val1_high, val1_low; + + val0_low = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CNT0_L(idx + 1)); + val0_high = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CNT0_H(idx + 1)); + *cnt0 = val0_high << UMMU_PMCG_CFGR_SIZE | val0_low; + + val1_low = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CNT1_L(idx + 1)); + val1_high = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CNT1_H(idx + 1)); + *cnt1 = val1_high << UMMU_PMCG_CFGR_SIZE | val1_low; +} + +static void ummu_pmu_counter_set_value(struct ummu_pmu *ummu_pmu, int idx, + uint64_t pre_cnt0, uint64_t pre_cnt1) +{ + uint64_t val0_high, val0_low; + uint64_t val1_high, val1_low; + + val0_low = pre_cnt0 & GENMASK_ULL(UMMU_COUNTER_LEN, 0); + val0_high = pre_cnt0 >> UMMU_PMCG_CFGR_SIZE; + val1_low = pre_cnt1 & GENMASK_ULL(UMMU_COUNTER_LEN, 0); + val1_high = pre_cnt1 >> UMMU_PMCG_CFGR_SIZE; + + writel_relaxed(val0_low, ummu_pmu->reg_base + UMMU_PMCG_COM_CNT0_L(idx + 1)); + writel_relaxed(val0_high, ummu_pmu->reg_base + UMMU_PMCG_COM_CNT0_H(idx + 1)); + writel_relaxed(val1_low, ummu_pmu->reg_base + UMMU_PMCG_COM_CNT1_L(idx + 1)); + writel_relaxed(val1_high, ummu_pmu->reg_base + UMMU_PMCG_COM_CNT1_H(idx + 1)); +} + +static void ummu_pmu_counter_enable(struct ummu_pmu *ummu_pmu, int idx) +{ + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); + val |= UMMU_PMCG_COM_CTRL_EVENT_EN; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); +} + +static void ummu_pmu_counter_disable(struct ummu_pmu *ummu_pmu, int idx) +{ + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); + val &= ~UMMU_PMCG_COM_CTRL_EVENT_EN; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); +} + +static void ummu_pmu_counter_clear_enable(struct ummu_pmu *ummu_pmu, int idx) +{ + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); + val |= UMMU_PMCG_COM_CTRL_EVENT_CLR; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); +} + +static void ummu_pmu_counter_clear_disable(struct ummu_pmu *ummu_pmu, int idx) +{ + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); + val &= ~UMMU_PMCG_COM_CTRL_EVENT_CLR; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); +} + +static bool ummu_pmu_validate_event_count(struct perf_event *event) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct perf_cpu_pmu_context *cpc; + + cpc = per_cpu_ptr(event->pmu->cpu_pmu_context, ummu_pmu->on_cpu); + if (!cpc) { + dev_err(ummu_pmu->dev, "cpc is null.\n"); + return false; + } + + /* The events count must less than the counters in the HW */ + return cpc->epc.nr_events < ummu_pmu->num_counters; +} + +static int ummu_pmu_event_init(struct perf_event *event) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + struct device *dev = ummu_pmu->dev; + uint32_t pre_cnt1_idx; + + if (event->attr.type != event->pmu->type) + return -ENOENT; + + if (hwc->sample_period) { + dev_err(dev, "sampling not supported!\n"); + return -EOPNOTSUPP; + } + + if (event->cpu < 0) { + dev_err(dev, "per-task mode not supported!\n"); + return -EOPNOTSUPP; + } + + if (event->attach_state & PERF_ATTACH_TASK) { + dev_err(dev, "per-task counter cannot allocate!\n"); + return -EOPNOTSUPP; + } + + /* + * Validate if the events in group does not exceed the + * available counters in hardware. + */ + if (!ummu_pmu_validate_event_count(event)) { + dev_err(dev, "event count exceeds the available counters!\n"); + return -EINVAL; + } + + /* Clear the allocated counter */ + hwc->idx = -1; + + /* + * Ensure all events are on the same cpu so all events are in the + * same cpu context, to avoid races on pmu_enable etc. + */ + event->cpu = ummu_pmu->on_cpu; + + pre_cnt1_idx = get_local_pre_count_idx(get_event(event)); + if (pre_cnt1_idx >= PRE_CNT1_MAX_IDX) { + dev_err(dev, "pre counter1 index invalid, event maybe wrong.\n"); + return -EINVAL; + } + + local64_set(&hwc->prev_count, 0); + local64_set(&pre_count1[pre_cnt1_idx], 0); + + return 0; +} + +static void ummu_pmu_config_event_type(struct ummu_pmu *ummu_pmu, + struct perf_event *event, int idx) +{ + uint32_t type = get_event(event) & UMMU_PMCG_COM_CTRL_EVENT_CODE; + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); + val &= ~UMMU_PMCG_COM_CTRL_EVENT_CODE; + val |= type; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); +} + +static void ummu_pmu_config_event_filter(struct ummu_pmu *ummu_pmu, + struct perf_event *event, int idx) +{ + uint32_t user_def_id = get_user_define_id(event); + uint32_t master_id = get_master_id(event); + uint32_t tect_tag = get_tect_tag(event); + uint32_t tid = get_token_id(event); + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_FILT_COND_L(idx + 1)); + if (tid) + val |= tid; + if (master_id) + val |= master_id << UMMU_PMCG_MASTER_ID_SHIFT; + + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_FILT_COND_L(idx + 1)); + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_FILT_COND_H(idx + 1)); + if (tect_tag) + val |= tect_tag; + if (user_def_id) + val |= user_def_id << UMMU_PMCG_USER_DEF_ID_SHIFT; + + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_FILT_COND_H(idx + 1)); +} + +static void ummu_pmu_config_event_filter_mask(struct ummu_pmu *ummu_pmu, + struct perf_event *event, int idx) +{ + uint32_t val, tid_msk, master_id_msk, tect_tag_msk, user_def_id_msk; + + tid_msk = get_token_id_flt_msk(event); + master_id_msk = get_master_id_flt_msk(event); + tect_tag_msk = get_tect_tag_flt_msk(event); + user_def_id_msk = get_user_define_id_flt_msk(event); + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_FILT_MSK_L(idx + 1)); + + if (tid_msk) { + val &= ~UMMU_PMCG_FILT_MSK_L_TOKEN_ID; + val |= tid_msk; + } + if (master_id_msk) { + val &= ~UMMU_PMCG_FILT_MSK_L_MASTER_ID; + val |= master_id_msk << UMMU_PMCG_MASTER_ID_MSK_SHIFT; + } + + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_FILT_MSK_L(idx + 1)); + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_FILT_MSK_H(idx + 1)); + + if (tect_tag_msk) { + val &= ~UMMU_PMCG_FILT_COND_H_TECT_TAG; + val |= tect_tag_msk; + } + if (user_def_id_msk) { + val &= ~UMMU_PMCG_FILT_COND_H_USER_DEF_ID; + val |= user_def_id_msk << UMMU_PMCG_USER_DEF_ID_MSK_SHIFT; + } + + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_FILT_MSK_H(idx + 1)); +} + +static void ummu_pmu_config_event(struct ummu_pmu *ummu_pmu, + struct perf_event *event, int idx) +{ + ummu_pmu_config_event_type(ummu_pmu, event, idx); + ummu_pmu_config_event_filter(ummu_pmu, event, idx); + ummu_pmu_config_event_filter_mask(ummu_pmu, event, idx); +} + +static void ummu_pmu_config_overflow_int_mask(struct ummu_pmu *ummu_pmu, + struct perf_event *event, + int idx) +{ + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_OVF_INT_MSK(idx + 1)); + val &= ~UMMU_PMCG_CNT0_OVERFLOW_INT_MSK; + val &= ~UMMU_PMCG_CNT1_OVERFLOW_INT_MSK; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_OVF_INT_MSK(idx + 1)); +} + +static void ummu_pmu_event_start(struct perf_event *event, int flags) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + uint64_t prev_count0, prev_count1; + int idx = hwc->idx; + + if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) + return; + + WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); + hwc->state = 0; + + ummu_pmu_config_event(ummu_pmu, event, idx); + + if (flags & PERF_EF_RELOAD) { + prev_count0 = (uint64_t)local64_read(&hwc->prev_count); + prev_count1 = (uint64_t)local64_read(PRE_CNT1_ADDR(idx)); + ummu_pmu_counter_set_value(ummu_pmu, idx, prev_count0, prev_count1); + } + + ummu_pmu_counter_enable(ummu_pmu, idx); + ummu_pmu_config_overflow_int_mask(ummu_pmu, event, idx); + /* Propagate changes to the userspace mapping. */ + perf_event_update_userpage(event); +} + +static uint64_t ummu_pmu_calculate(uint8_t event_id, uint64_t cnt0, + uint64_t cnt1) +{ + uint8_t event_type = event_id >> UMMU_EVTYPE_SHIFT; + uint64_t result; + + switch (event_type) { + case UMMU_PMU_EVENT_TYPE_NUM: + result = cnt0; + break; + case UMMU_PMU_EVENT_TYPE_HIT_RATE: + case UMMU_PMU_EVENT_TYPE_FREQ: + case UMMU_PMU_EVENT_TYPE_AVER_LATENCY: + case UMMU_PMU_EVENT_TYPE_BUF_OCCUPY_RATE: + if (!cnt0 || !cnt1) + return 0; + result = cnt0 * UMMU_PMU_MULTIPLES_RATE / cnt1; + break; + default: + result = 0; + break; + } + + return result; +} + +static void ummu_pmu_event_update(struct perf_event *event) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + uint64_t new_cnt0, prev_cnt0, new_cnt1; + int idx = hwc->idx; + + do { + prev_cnt0 = local64_read(&hwc->prev_count); + ummu_pmu_counter_get_value(ummu_pmu, idx, &new_cnt0, &new_cnt1); + } while (local64_cmpxchg(&hwc->prev_count, prev_cnt0, new_cnt0) != + prev_cnt0); + + local64_set(PRE_CNT1_ADDR(idx), new_cnt1); + + local64_set(&event->count, + ummu_pmu_calculate(get_event(event), new_cnt0, new_cnt1)); +} + +static void ummu_pmu_reset_config_event_type(struct ummu_pmu *ummu_pmu, + struct perf_event *event, int idx) +{ + uint32_t val; + + val = readl_relaxed(ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); + val &= ~UMMU_PMCG_COM_CTRL_EVENT_CODE; + writel_relaxed(val, ummu_pmu->reg_base + UMMU_PMCG_COM_CTRL(idx + 1)); +} + +static void ummu_pmu_reset_config_event_filter(struct ummu_pmu *ummu_pmu, + struct perf_event *event, + int idx) +{ + /* register default value equals to 0x0 */ + writel_relaxed(0, ummu_pmu->reg_base + UMMU_PMCG_FILT_COND_L(idx + 1)); + writel_relaxed(0, ummu_pmu->reg_base + UMMU_PMCG_FILT_COND_H(idx + 1)); +} + +static void ummu_pmu_reset_config_event_filter_mask(struct ummu_pmu *ummu_pmu, + struct perf_event *event, + int idx) +{ + /* register default value equals to 0x0000000000000000 */ + writel_relaxed(0, ummu_pmu->reg_base + UMMU_PMCG_FILT_MSK_L(idx + 1)); + + writel_relaxed(0, ummu_pmu->reg_base + UMMU_PMCG_FILT_MSK_H(idx + 1)); +} + +static void ummu_pmu_reset_config_event(struct ummu_pmu *ummu_pmu, + struct perf_event *event, int idx) +{ + ummu_pmu_reset_config_event_type(ummu_pmu, event, idx); + ummu_pmu_config_overflow_int_mask(ummu_pmu, event, idx); + ummu_pmu_reset_config_event_filter(ummu_pmu, event, idx); + ummu_pmu_reset_config_event_filter_mask(ummu_pmu, event, idx); +} + +static void ummu_pmu_event_stop(struct perf_event *event, int flags) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + if (hwc->state & PERF_HES_STOPPED) + return; + + ummu_pmu_counter_disable(ummu_pmu, idx); + ummu_pmu_reset_config_event(ummu_pmu, event, idx); + + /* Read hardware counter and update the perf counter statistics */ + ummu_pmu_event_update(event); + hwc->state = hwc->state | PERF_HES_STOPPED | PERF_HES_UPTODATE; +} + +static int ummu_pmu_get_event_idx(struct ummu_pmu *ummu_pmu, + struct perf_event *event) +{ + uint32_t num_counts = ummu_pmu->num_counters; + int idx; + + idx = find_first_zero_bit(ummu_pmu->used_counters, num_counts); + if (idx == num_counts) + /* The counters are all in use. */ + return -EAGAIN; + + set_bit(idx, ummu_pmu->used_counters); + + return idx; +} + +static void ummu_pmu_clear_counter(struct ummu_pmu *ummu_pmu, int idx) +{ + ummu_pmu_counter_clear_enable(ummu_pmu, idx); + ummu_pmu_counter_clear_disable(ummu_pmu, idx); +} + +static int ummu_pmu_event_add(struct perf_event *event, int flags) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx; + + /* Get an available counter index for counting */ + idx = ummu_pmu_get_event_idx(ummu_pmu, event); + if (idx < 0 || idx >= UMMU_PMCG_MAX_COUNTERS) + return -EINVAL; + + pre_count1_idx[idx] = get_local_pre_count_idx(get_event(event)); + if (pre_count1_idx[idx] >= PRE_CNT1_MAX_IDX) { + dev_err(ummu_pmu->dev, "pre counter1 [%u] index invalid, event maybe wrong.\n", + get_event(event)); + return -EINVAL; + } + + ummu_pmu_clear_counter(ummu_pmu, idx); + + ummu_pmu->events[idx] = event; + hwc->idx = idx; + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; + + if (flags & PERF_EF_START) + ummu_pmu_event_start(event, PERF_EF_RELOAD); + + dev_dbg(ummu_pmu->dev, "pmu event [0x%x] idx [%d] pre_cnt1 idx [%u] add finished.\n", + get_event(event), idx, pre_count1_idx[idx]); + + return 0; +} + +static void ummu_pmu_event_del(struct perf_event *event, int flags) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + if (idx < 0) { + dev_err(ummu_pmu->dev, "event no exist!\n"); + return; + } + + ummu_pmu_event_stop(event, flags | PERF_EF_UPDATE); + ummu_pmu->events[idx] = NULL; + pre_count1_idx[idx] = PRE_CNT1_MAX_IDX; + clear_bit(idx, ummu_pmu->used_counters); + + perf_event_update_userpage(event); +} + +static void ummu_pmu_event_read(struct perf_event *event) +{ + ummu_pmu_event_update(event); +} + +/* cpumask attributes */ +static ssize_t ummu_pmu_cpumask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ummu_pmu *ummu_pmu = to_ummu_pmu(dev_get_drvdata(dev)); + + return cpumap_print_to_pagebuf(true, buf, cpumask_of(ummu_pmu->on_cpu)); +} + +static struct device_attribute ummu_pmu_cpumask_attr = + __ATTR(cpumask, 0444, ummu_pmu_cpumask_show, NULL); + +static struct attribute *ummu_pmu_cpumask_attrs[] = { + &ummu_pmu_cpumask_attr.attr, + NULL +}; + +static const struct attribute_group ummu_pmu_cpumask_attr_group = { + .attrs = ummu_pmu_cpumask_attrs +}; + +/* format attributes */ +static ssize_t ummu_pmu_format_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + return sysfs_emit(buf, "%s\n", (char *)eattr->var); +} + +#define UMMU_PMU_FORMAT(_name, _func, _config) \ + (&((struct dev_ext_attribute[]){ \ + { __ATTR(_name, 0444, _func, NULL), _config} })[0] \ + .attr.attr) + +#define UMMU_PMU_FORMAT_ATTR(_name, _format) \ + UMMU_PMU_FORMAT(_name, ummu_pmu_format_show, _format) + +static struct attribute *ummu_pmu_formats_attrs[] = { + UMMU_PMU_FORMAT_ATTR(event, "config:0-7"), + UMMU_PMU_FORMAT_ATTR(token_id, "config1:0-19"), + UMMU_PMU_FORMAT_ATTR(master_id, "config1:20-31"), + UMMU_PMU_FORMAT_ATTR(tect_tag, "config1:32-47"), + UMMU_PMU_FORMAT_ATTR(user_define_id, "config1:48-63"), + UMMU_PMU_FORMAT_ATTR(token_id_flt_msk, "config2:0-19"), + UMMU_PMU_FORMAT_ATTR(master_id_flt_msk, "config2:20-31"), + UMMU_PMU_FORMAT_ATTR(tect_tag_flt_msk, "config2:32-47"), + UMMU_PMU_FORMAT_ATTR(user_define_id_flt_msk, "config2:48-63"), + NULL +}; + +static const struct attribute_group ummu_pmu_format_attr_group = { + .name = "format", + .attrs = ummu_pmu_formats_attrs +}; + +static int ummu_pmu_check_usi_mpam_cap(struct ummu_pmu *pmu, int partid, int pmg) +{ + if (partid < 0 || partid > UMMU_PMCG_USI_MPAM_PARTID_MAX) { + dev_err(pmu->dev, "usi_mpam partid exceeds range: partid[0, %d]\n", + UMMU_PMCG_USI_MPAM_PARTID_MAX); + return -ERANGE; + } + + if (pmg < 0 || pmg > UMMU_PMCG_USI_MPAM_PMG_MAX) { + dev_err(pmu->dev, "usi_mpam pmg exceeds range: pmg[0, %d]\n", + UMMU_PMCG_USI_MPAM_PMG_MAX); + return -ERANGE; + } + + return 0; +} + +static const struct attribute_group *ummu_pmu_attr_groups[] = { + &ummu_pmu_cpumask_attr_group, + &ummu_pmu_events_attr_group, + &ummu_pmu_format_attr_group, + NULL +}; + +static int ummu_pmu_set_usi_mpam(struct ummu_pmu *pmu, int partid, int pmg) +{ + uint32_t reg; + int ret; + + if (!pmu) + return -EINVAL; + + ret = ummu_pmu_check_usi_mpam_cap(pmu, partid, pmg); + if (ret) + return ret; + + reg = UMMU_PMCG_USI_MPAM_NS; + reg |= FIELD_PREP(UMMU_PMCG_USI_MPAM_PMG, pmg); + reg |= FIELD_PREP(UMMU_PMCG_USI_MPAM_PARTID, partid); + writel_relaxed(reg, pmu->reg_base + UMMU_PMCG_USI_MPAM); + + return 0; +} + +static int ummu_pmu_get_usi_mpam(struct ummu_pmu *pmu, int *partid, int *pmg) +{ + uint32_t reg; + + if (!pmu) + return -EINVAL; + + reg = readl_relaxed(pmu->reg_base + UMMU_PMCG_USI_MPAM); + *partid = FIELD_GET(UMMU_PMCG_USI_MPAM_PARTID, reg); + *pmg = FIELD_GET(UMMU_PMCG_USI_MPAM_PMG, reg); + return 0; +} + +static int ummu_pmu_offline_cpu(uint32_t cpu, struct hlist_node *node) +{ + struct ummu_pmu *ummu_pmu; + uint32_t target; + + ummu_pmu = hlist_entry_safe(node, struct ummu_pmu, node); + if (!ummu_pmu) + return -ENODEV; + + /* Nothing to do if this CPU doesn't own the PMU */ + if (cpu != ummu_pmu->on_cpu) + return 0; + + /* Choose a new CPU from all online cpus */ + target = cpumask_any_but(cpu_online_mask, cpu); + if (target >= nr_cpu_ids) + return 0; + + perf_pmu_migrate_context(&ummu_pmu->pmu, cpu, target); + /* Use this CPU for event counting */ + ummu_pmu->on_cpu = target; + + return 0; +} + +static void ummu_pmu_init_ops(struct ummu_pmu *ummu_pmu, struct module *module) +{ + ummu_pmu->pmu = (struct pmu) { + .module = module, + .attr_groups = ummu_pmu_attr_groups, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + .task_ctx_nr = perf_invalid_context, + .pmu_enable = ummu_pmu_enable, + .pmu_disable = ummu_pmu_disable, + .event_init = ummu_pmu_event_init, + .add = ummu_pmu_event_add, + .del = ummu_pmu_event_del, + .start = ummu_pmu_event_start, + .stop = ummu_pmu_event_stop, + .read = ummu_pmu_event_read + }; +} + +static int ummu_pmu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ummu_pmu *ummu_pmu; + struct resource *res; + char *name; + int ret; + + ummu_pmu = devm_kzalloc(dev, sizeof(*ummu_pmu), GFP_KERNEL); + if (!ummu_pmu) + return dev_err_probe(dev, -ENOMEM, "failed to kzalloc for pmu\n"); + + ummu_pmu->dev = dev; + platform_set_drvdata(pdev, ummu_pmu); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(res == NULL)) + return dev_err_probe(dev, -EINVAL, "IO resource is null\n"); + + ummu_pmu->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ummu_pmu->reg_base)) + return dev_err_probe(dev, PTR_ERR(ummu_pmu->reg_base), "Ioremap failed\n"); + + name = devm_kasprintf(dev, GFP_KERNEL, "ummu_pmcg_%d", + atomic_inc_return(&ummu_pmu_index) - 1); + if (!name) + return dev_err_probe(dev, -ENOMEM, "Create ummu pmu name failed\n"); + + ret = ummu_pmu_reset(ummu_pmu); + if (ret) + return dev_err_probe(dev, ret, "reset pmu failed\n"); + + ummu_pmu_init_ops(ummu_pmu, THIS_MODULE); + ret = perf_pmu_register(&ummu_pmu->pmu, name, -1); + if (ret) { + cpuhp_state_remove_instance_nocalls( + (enum cpuhp_state)ummu_pmu_cpuhp_state, + &ummu_pmu->node); + return dev_err_probe(dev, ret, "registering PMU failed\n"); + } + + return 0; +} + +static int ummu_pmu_remove(struct platform_device *pdev) +{ + struct ummu_pmu *ummu_pmu = platform_get_drvdata(pdev); + + if (!ummu_pmu) { + pr_err("pmu device remove get invalid platform device!\n"); + return -ENODEV; + } + + perf_pmu_unregister(&ummu_pmu->pmu); + cpuhp_state_remove_instance_nocalls( + (enum cpuhp_state)ummu_pmu_cpuhp_state, &ummu_pmu->node); + + return 0; +} + +static void ummu_pmu_shutdown(struct platform_device *pdev) +{ + struct ummu_pmu *ummu_pmu = platform_get_drvdata(pdev); + + if (!ummu_pmu) { + pr_err("pmu device shutdown get invalid platform device!\n"); + return; + } + + ummu_pmu_disable(&ummu_pmu->pmu); +} + +static const struct of_device_id hisi_ummu_pmu_of_match[] = { + { .compatible = "ub,ummu_pmu", }, + { } +}; +MODULE_DEVICE_TABLE(of, hisi_ummu_pmu_of_match); + +static const struct acpi_device_id hisi_ummu_pmu_acpi_match[] = { + {"HISI0571", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hisi_ummu_pmu_acpi_match); + +static ssize_t partid_store(struct device *kobj, struct device_attribute *attr, + const char *buf, size_t count) +{ + int var, ret; + + ret = kstrtoint(buf, NUMBER_BASE_DECIMAL, &var); + if (ret < 0) + return ret; + + g_partid = var; + dev_info(kobj, "ummu mpam input partid = %d.\n", var); + return count; +} + +static ssize_t pmg_store(struct device *kobj, struct device_attribute *attr, + const char *buf, size_t count) +{ + int var, ret; + + ret = kstrtoint(buf, NUMBER_BASE_DECIMAL, &var); + if (ret < 0) + return ret; + + g_pmg = var; + dev_info(kobj, "ummu mpam input pmg = %d.\n", var); + return count; +} + +static int check_input_buf(const char *buf) +{ + int var, ret; + + ret = kstrtoint(buf, NUMBER_BASE_DECIMAL, &var); + if (ret) + return ret; + + if (var != 1) + return -EINVAL; + + return 0; +} + +static ssize_t run_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ummu_pmu *pmu; + int ret; + + ret = check_input_buf(buf); + if (ret) + return ret; + + pmu = (struct ummu_pmu *)dev_get_drvdata(dev); + ret = ummu_pmu_set_usi_mpam(pmu, g_partid, g_pmg); + if (ret) { + dev_err(dev, + "ummu_pmu set usi_mpam(partid[%d], pmg[%d]) failed, ret[%d]\n", + g_partid, g_pmg, ret); + return ret; + } + + return count; +} + +static ssize_t mpam_info_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ummu_pmu *pmu = (struct ummu_pmu *)dev_get_drvdata(dev); + int partid = 0, pmg = 0, ret; + + ret = ummu_pmu_get_usi_mpam(pmu, &partid, &pmg); + if (ret) + return ret; + return sysfs_emit(buf, "partid:%d\npmg:%d\n", partid, pmg); +} + +static DEVICE_ATTR_WO(partid); +static DEVICE_ATTR_WO(pmg); +static DEVICE_ATTR_WO(run); +static DEVICE_ATTR_RO(mpam_info); + +static struct attribute *ummu_pmu_usi_mpam_attrs[] = { + &dev_attr_partid.attr, + &dev_attr_pmg.attr, + &dev_attr_run.attr, + &dev_attr_mpam_info.attr, + NULL +}; + +static const struct attribute_group ummu_pmu_usi_mpam_attr_group = { + .name = "usi_mpam", + .attrs = ummu_pmu_usi_mpam_attrs +}; + +static const struct attribute_group *ummu_pmu_groups[] = { + &ummu_pmu_usi_mpam_attr_group, + NULL +}; + +static struct platform_driver ummu_pmu_driver = { + .driver = { + .name = UMMU_PMU_DRV_NAME, + .suppress_bind_attrs = true, + .of_match_table = hisi_ummu_pmu_of_match, + .acpi_match_table = hisi_ummu_pmu_acpi_match, + .dev_groups = ummu_pmu_groups + }, + .probe = ummu_pmu_probe, + .remove = ummu_pmu_remove, + .shutdown = ummu_pmu_shutdown +}; + +static int __init ummu_pmu_module_init(void) +{ + int ret; + + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "perf/ummu/pmcg:online", NULL, + ummu_pmu_offline_cpu); + if (ret < 0) { + pr_err("ummu pmu: setup hotplug failed, ret = %d\n", ret); + return ret; + } + ummu_pmu_cpuhp_state = ret; + + ret = platform_driver_register(&ummu_pmu_driver); + if (ret) + cpuhp_remove_multi_state( + (enum cpuhp_state)ummu_pmu_cpuhp_state); + + return ret; +} +module_init(ummu_pmu_module_init); + +static void __exit ummu_pmu_module_exit(void) +{ + platform_driver_unregister(&ummu_pmu_driver); + cpuhp_remove_multi_state((enum cpuhp_state)ummu_pmu_cpuhp_state); +} +module_exit(ummu_pmu_module_exit); + +MODULE_DESCRIPTION("PMU driver for UMMU Performance Monitors Extension"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: ubfi"); +MODULE_ALIAS("platform:" UMMU_PMU_DRV_NAME); diff --git a/drivers/ub/Kconfig b/drivers/ub/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..9755ce29deeaa08665da6b0d309856d0efe03f36 --- /dev/null +++ b/drivers/ub/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# UnifiedBus configuration +# + +menuconfig UB + bool "UB (UnifiedBus) support" + depends on ARM64 + default n + help + Support for UB. + If you have a hardware that support UB protocol, + Say y here. By Default this option is closed. + +if UB +source "drivers/ub/ubus/Kconfig" +source "drivers/ub/ubfi/Kconfig" +source "drivers/ub/ubase/Kconfig" +endif # UB diff --git a/drivers/ub/Makefile b/drivers/ub/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..72eead96aa9583fc5eba0155a9bc0d2f63fb62d9 --- /dev/null +++ b/drivers/ub/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-y += ubus/ +obj-y += ubfi/ +obj-$(CONFIG_UB_UBASE) += ubase/ diff --git a/drivers/ub/ubase/Kconfig b/drivers/ub/ubase/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..c9137f193a47c43b642bffec473aa4892ed89186 --- /dev/null +++ b/drivers/ub/ubase/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# ubase configuration +# + +menuconfig UB_UBASE + default n + tristate "UBASE support" + depends on UB_UBUS_BUS && UB_UBUS_USI + depends on UB_UMMU_CORE_DRIVER + help + This option enables support for the ubase module of Unifiedbus, + including support for getting resource and create auxiliary bus + for upper Unifiedbus modules like unic, udma and cdma. + Say 'Y' here if you want to enable Unifiedbus upper module. diff --git a/drivers/ub/ubase/Makefile b/drivers/ub/ubase/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..67440b08bcfe6e5ad1cb04af14745cc0f6f076b1 --- /dev/null +++ b/drivers/ub/ubase/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +## Makefile for the Hisilicon ubase drivers. +# +## + +ccflags-y += -I$(srctree)/$(src) +ccflags-y += -I$(srctree)/drivers/ub/ubase/debugfs + +MODULE_NAME := ubase + +UBASE_OBJS := ubase_main.o ubase_dev.o ubase_hw.o ubase_cmd.o ubase_ctrlq.o \ + debugfs/ubase_debugfs.o ubase_eq.o ubase_mailbox.o ubase_ubus.o \ + debugfs/ubase_ctx_debugfs.o debugfs/ubase_qos_debugfs.o \ + ubase_qos_hw.o ubase_tp.o ubase_ctrlq_tp.o ubase_reset.o \ + ubase_err_handle.o ubase_pmem.o ubase_stats.o ubase_arq.o + +$(MODULE_NAME)-objs := $(UBASE_OBJS) +obj-$(CONFIG_UB_UBASE) := ubase.o diff --git a/drivers/ub/ubase/debugfs/ubase_ctx_debugfs.c b/drivers/ub/ubase/debugfs/ubase_ctx_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..33221a90edd91a2a766c9348f1c3ca5dcf308a7e --- /dev/null +++ b/drivers/ub/ubase/debugfs/ubase_ctx_debugfs.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "ubase_debugfs.h" +#include "ubase_hw.h" +#include "ubase_mailbox.h" +#include "ubase_tp.h" +#include "ubase_ctx_debugfs.h" + +#define UBASE_DEFAULT_CTXGN 0 + +static void ubase_dump_eq_ctx(struct seq_file *s, struct ubase_eq *eq) +{ + seq_printf(s, "%-5u", eq->eqn); + seq_printf(s, "%-13u", eq->entries_num); + seq_printf(s, "%-7u", eq->state); + seq_printf(s, "%-8u", eq->arm_st); + seq_printf(s, "%-10u", eq->eqe_size); + seq_printf(s, "%-11u", eq->eq_period); + seq_printf(s, "%-14u", eq->coalesce_cnt); + seq_printf(s, "%-6u", eq->irqn); + seq_printf(s, "%-10u", eq->eqc_irqn); + seq_printf(s, "%-12u", eq->cons_index); + seq_puts(s, "\n"); +} + +static void ubase_eq_ctx_titles_print(struct seq_file *s) +{ + seq_puts(s, "EQN ENTRIES_NUM STATE ARM_ST EQE_SIZE EQ_PERIOD "); + seq_puts(s, "COALESCE_CNT IRQN EQC_IRQN CONS_INDEX\n"); +} + +static void ubase_dump_aeq_ctx(struct seq_file *s, struct ubase_dev *udev, u32 idx) +{ + struct ubase_aeq *aeq = &udev->irq_table.aeq; + struct ubase_eq *eq = &aeq->eq; + + ubase_dump_eq_ctx(s, eq); +} + +static void ubase_dump_ceq_ctx(struct seq_file *s, struct ubase_dev *udev, u32 idx) +{ + struct ubase_ceq *ceq = &udev->irq_table.ceqs.ceq[idx]; + struct ubase_eq *eq = &ceq->eq; + + ubase_dump_eq_ctx(s, eq); +} + +static void ubase_tpg_ctx_titles_print(struct seq_file *s) +{ + seq_puts(s, "CHANNEL_ID TPGN TP_SHIFT VALID_TP "); + seq_puts(s, "START_TPN TPG_STATE TP_CNT\n"); +} + +static void ubase_dump_tpg_ctx(struct seq_file *s, struct ubase_dev *udev, u32 idx) +{ + struct ubase_tpg *tpg = &udev->tp_ctx.tpg[idx]; + + seq_printf(s, "%-12u", idx); + seq_printf(s, "%-9u", tpg->mb_tpgn); + seq_printf(s, "%-10u", tpg->tp_shift); + seq_printf(s, "%-10lu", tpg->valid_tp); + seq_printf(s, "%-11u", tpg->start_tpn); + seq_printf(s, "%-11u", tpg->tpg_state); + seq_printf(s, "%-8u", tpg->tp_cnt); + seq_puts(s, "\n"); +} + +enum ubase_dbg_ctx_type { + UBASE_DBG_AEQ_CTX = 0, + UBASE_DBG_CEQ_CTX, + UBASE_DBG_TPG_CTX, + UBASE_DBG_TP_CTX, +}; + +static u32 ubase_get_ctx_num(struct ubase_dev *udev, + enum ubase_dbg_ctx_type ctx_type, u32 ctxgn) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + u32 ctx_num = 0; + + switch (ctx_type) { + case UBASE_DBG_AEQ_CTX: + ctx_num = udev->caps.dev_caps.num_aeq_vectors; + break; + case UBASE_DBG_CEQ_CTX: + ctx_num = udev->irq_table.ceqs.num; + break; + case UBASE_DBG_TPG_CTX: + ctx_num = unic_caps->tpg.max_cnt; + break; + case UBASE_DBG_TP_CTX: + spin_lock(&udev->tp_ctx.tpg_lock); + if (udev->tp_ctx.tpg) + ctx_num = udev->tp_ctx.tpg[ctxgn].tp_cnt; + spin_unlock(&udev->tp_ctx.tpg_lock); + break; + default: + ubase_err(udev, "failed to get ctx num, ctx_type = %u.\n", + ctx_type); + break; + } + + return ctx_num; +} + +static int ubase_dbg_dump_context(struct seq_file *s, + enum ubase_dbg_ctx_type ctx_type) +{ + struct ubase_dbg_ctx { + void (*print_ctx_titles)(struct seq_file *s); + void (*get_ctx)(struct seq_file *s, struct ubase_dev *udev, u32 idx); + } dbg_ctx[] = { + {ubase_eq_ctx_titles_print, ubase_dump_aeq_ctx}, + {ubase_eq_ctx_titles_print, ubase_dump_ceq_ctx}, + {ubase_tpg_ctx_titles_print, ubase_dump_tpg_ctx}, + }; + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + unsigned long port_bitmap; + u32 tp_pos, i; + + dbg_ctx[ctx_type].print_ctx_titles(s); + + port_bitmap = unic_caps->utp_port_bitmap; + for (i = 0; i < ubase_get_ctx_num(udev, ctx_type, UBASE_DEFAULT_CTXGN); i++) { + if (ctx_type != UBASE_DBG_TP_CTX) { + dbg_ctx[ctx_type].get_ctx(s, udev, i); + continue; + } + + tp_pos = (i % unic_caps->tpg.depth) * UBASE_TP_PORT_BITMAP_STEP; + if (test_bit(tp_pos, &port_bitmap)) + dbg_ctx[ctx_type].get_ctx(s, udev, i); + } + + return 0; +} + +struct ubase_ctx_info { + u32 start_idx; + u32 ctx_size; + u8 op; + const char *ctx_name; +}; + +static inline u32 ubase_get_ctx_group_num(struct ubase_dev *udev, + enum ubase_dbg_ctx_type ctx_type) +{ + if (ctx_type == UBASE_DBG_TP_CTX) + return udev->caps.unic_caps.tpg.max_cnt; + + return 1; +} + +static void ubase_get_ctx_info(struct ubase_dev *udev, + enum ubase_dbg_ctx_type ctx_type, + struct ubase_ctx_info *ctx_info, u32 ctxgn) +{ + switch (ctx_type) { + case UBASE_DBG_AEQ_CTX: + ctx_info->start_idx = 0; + ctx_info->ctx_size = UBASE_AEQ_CTX_SIZE; + ctx_info->op = UBASE_MB_QUERY_AEQ_CONTEXT; + ctx_info->ctx_name = "aeq"; + break; + case UBASE_DBG_CEQ_CTX: + ctx_info->start_idx = 0; + ctx_info->ctx_size = UBASE_CEQ_CTX_SIZE; + ctx_info->op = UBASE_MB_QUERY_CEQ_CONTEXT; + ctx_info->ctx_name = "ceq"; + break; + case UBASE_DBG_TPG_CTX: + ctx_info->start_idx = udev->caps.unic_caps.tpg.start_idx; + ctx_info->ctx_size = udev->ctx_buf.tpg.entry_size; + ctx_info->op = UBASE_MB_QUERY_TPG_CONTEXT; + ctx_info->ctx_name = "tpg"; + break; + case UBASE_DBG_TP_CTX: + spin_lock(&udev->tp_ctx.tpg_lock); + ctx_info->start_idx = udev->tp_ctx.tpg ? + udev->tp_ctx.tpg[ctxgn].start_tpn : 0; + spin_unlock(&udev->tp_ctx.tpg_lock); + + ctx_info->ctx_size = udev->ctx_buf.tp.entry_size; + ctx_info->op = UBASE_MB_QUERY_TP_CONTEXT; + ctx_info->ctx_name = "tp"; + break; + default: + ubase_err(udev, "failed to get ctx info, ctx_type = %u.\n", + ctx_type); + break; + } +} + +static void ubase_mask_eq_ctx_key_words(void *buf) +{ + struct ubase_eq_ctx *eq = (struct ubase_eq_ctx *)buf; + + eq->eqe_base_addr_l = 0; + eq->eqe_base_addr_h = 0; + eq->eqe_token_id = 0; + eq->eqe_token_value = 0; +} + +static void ubase_mask_tp_ctx_key_words(void *buf) +{ + struct ubase_tp_ctx *tp = (struct ubase_tp_ctx *)buf; + + tp->wqe_ba_l = 0; + tp->wqe_ba_h = 0; + tp->tp_wqe_token_id = 0; + tp->reorder_q_addr_l = 0; + tp->reorder_q_addr_h = 0; + tp->scc_token = 0; + tp->scc_token_1 = 0; +} + +static void ubase_mask_ctx_key_words(void *buf, + enum ubase_dbg_ctx_type ctx_type) +{ + switch (ctx_type) { + case UBASE_DBG_AEQ_CTX: + case UBASE_DBG_CEQ_CTX: + ubase_mask_eq_ctx_key_words(buf); + break; + case UBASE_DBG_TPG_CTX: + break; + case UBASE_DBG_TP_CTX: + ubase_mask_tp_ctx_key_words(buf); + break; + default: + break; + } +} + +static void __ubase_print_context_hw(struct seq_file *s, void *ctx_addr, + u32 ctx_len) +{ + __le32 *p = (__le32 *)ctx_addr; + u32 i; + + ctx_len = ctx_len / sizeof(u32); + for (i = 0; i < ctx_len; i++, p++) { + seq_printf(s, "%lu\t", (i + 1) * sizeof(u32)); + seq_printf(s, "%08x\n", le32_to_cpu(*p)); + } +} + +void ubase_print_context_hw(struct seq_file *s, void *ctx_addr, u32 ctx_len) +{ + if (!s || !ctx_addr) + return; + + __ubase_print_context_hw(s, ctx_addr, ctx_len); +} +EXPORT_SYMBOL(ubase_print_context_hw); + +static int ubase_dbg_dump_ctx_hw(struct seq_file *s, void *data, + enum ubase_dbg_ctx_type ctx_type) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_ctx_info ctx_info = {0}; + struct ubase_cmd_mailbox *mailbox; + u32 max_ctxgn, ctxn, ctxgn; + struct ubase_mbx_attr attr; + int ret = 0; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + mailbox = __ubase_alloc_cmd_mailbox(udev); + if (IS_ERR_OR_NULL(mailbox)) { + ubase_err(udev, + "failed to alloc mailbox for dump hw context.\n"); + return -ENOMEM; + } + + max_ctxgn = ubase_get_ctx_group_num(udev, ctx_type); + for (ctxgn = 0; ctxgn < max_ctxgn; ctxgn++) { + ubase_get_ctx_info(udev, ctx_type, &ctx_info, ctxgn); + + for (ctxn = 0; ctxn < ubase_get_ctx_num(udev, ctx_type, ctxgn); ctxn++) { + ubase_fill_mbx_attr(&attr, ctxn + ctx_info.start_idx, + ctx_info.op, 0); + ret = __ubase_hw_upgrade_ctx_ex(udev, &attr, mailbox); + if (ret) { + ubase_err(udev, + "failed to post query %s ctx mbx, ret = %d.\n", + ctx_info.ctx_name, ret); + goto upgrade_ctx_err; + } + + seq_printf(s, "offset\t%s%u\n", ctx_info.ctx_name, + ctxn + ctx_info.start_idx); + ubase_mask_ctx_key_words(mailbox->buf, ctx_type); + __ubase_print_context_hw(s, mailbox->buf, ctx_info.ctx_size); + seq_puts(s, "\n"); + } + } + +upgrade_ctx_err: + __ubase_free_cmd_mailbox(udev, mailbox); + + return ret; +} + +int ubase_dbg_dump_aeq_context(struct seq_file *s, void *data) +{ + return ubase_dbg_dump_context(s, UBASE_DBG_AEQ_CTX); +} + +int ubase_dbg_dump_ceq_context(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + int ret; + + if (!mutex_trylock(&udev->irq_table.ceq_lock)) + return -EBUSY; + + if (!udev->irq_table.ceqs.ceq) { + mutex_unlock(&udev->irq_table.ceq_lock); + return -EBUSY; + } + + ret = ubase_dbg_dump_context(s, UBASE_DBG_CEQ_CTX); + mutex_unlock(&udev->irq_table.ceq_lock); + + return ret; +} + +int ubase_dbg_dump_tpg_ctx(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits)) + return -EBUSY; + + if (!ubase_get_ctx_num(udev, UBASE_DBG_TPG_CTX, UBASE_DEFAULT_CTXGN)) + return -EOPNOTSUPP; + + if (!spin_trylock(&udev->tp_ctx.tpg_lock)) + return -EBUSY; + + if (!udev->tp_ctx.tpg) { + spin_unlock(&udev->tp_ctx.tpg_lock); + return -EBUSY; + } + + ret = ubase_dbg_dump_context(s, UBASE_DBG_TPG_CTX); + spin_unlock(&udev->tp_ctx.tpg_lock); + + return ret; +} + +int ubase_dbg_dump_tpg_ctx_hw(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits)) + return -EBUSY; + + if (!ubase_get_ctx_num(udev, UBASE_DBG_TPG_CTX, UBASE_DEFAULT_CTXGN)) + return -EOPNOTSUPP; + + return ubase_dbg_dump_ctx_hw(s, data, UBASE_DBG_TPG_CTX); +} + +int ubase_dbg_dump_tp_ctx_hw(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits)) + return -EBUSY; + + if (!ubase_get_ctx_num(udev, UBASE_DBG_TP_CTX, UBASE_DEFAULT_CTXGN)) + return -EOPNOTSUPP; + + if (!ubase_get_ctx_group_num(udev, UBASE_DBG_TP_CTX)) + return -EOPNOTSUPP; + + return ubase_dbg_dump_ctx_hw(s, data, UBASE_DBG_TP_CTX); +} + +int ubase_dbg_dump_aeq_ctx_hw(struct seq_file *s, void *data) +{ + return ubase_dbg_dump_ctx_hw(s, data, UBASE_DBG_AEQ_CTX); +} + +int ubase_dbg_dump_ceq_ctx_hw(struct seq_file *s, void *data) +{ + return ubase_dbg_dump_ctx_hw(s, data, UBASE_DBG_CEQ_CTX); +} diff --git a/drivers/ub/ubase/debugfs/ubase_ctx_debugfs.h b/drivers/ub/ubase/debugfs/ubase_ctx_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..532665141fc8d3e8d40b433224e3d70e39269ed1 --- /dev/null +++ b/drivers/ub/ubase/debugfs/ubase_ctx_debugfs.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_CTX_DEBUGFS_H__ +#define __UBASE_CTX_DEBUGFS_H__ + +struct device; + +int ubase_dbg_dump_aeq_context(struct seq_file *s, void *data); +int ubase_dbg_dump_ceq_context(struct seq_file *s, void *data); +int ubase_dbg_dump_tpg_ctx(struct seq_file *s, void *data); +int ubase_dbg_dump_tp_ctx_hw(struct seq_file *s, void *data); +int ubase_dbg_dump_tpg_ctx_hw(struct seq_file *s, void *data); +int ubase_dbg_dump_aeq_ctx_hw(struct seq_file *s, void *data); +int ubase_dbg_dump_ceq_ctx_hw(struct seq_file *s, void *data); + +#endif diff --git a/drivers/ub/ubase/debugfs/ubase_debugfs.c b/drivers/ub/ubase/debugfs/ubase_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..ad97d7a58188fcb77027db915e8412b5f5960dd7 --- /dev/null +++ b/drivers/ub/ubase/debugfs/ubase_debugfs.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include + +#include "ubase_cmd.h" +#include "ubase_ctx_debugfs.h" +#include "ubase_dev.h" +#include "ubase_hw.h" +#include "ubase_qos_debugfs.h" +#include "ubase_stats.h" +#include "ubase_debugfs.h" + +static struct dentry *ubase_dbgfs_root; + +static int ubase_dbg_dump_rst_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + + seq_printf(s, "ELR reset count: %u\n", udev->reset_stat.elr_reset_cnt); + seq_printf(s, "port reset count: %u\n", udev->reset_stat.port_reset_cnt); + seq_printf(s, "reset done count: %u\n", udev->reset_stat.reset_done_cnt); + seq_printf(s, "HW reset done count: %u\n", udev->reset_stat.hw_reset_done_cnt); + seq_printf(s, "reset fail count: %u\n", udev->reset_stat.reset_fail_cnt); + seq_printf(s, "udev state: 0x%lx\n", udev->state_bits); + + return 0; +} + +static void ubase_dbg_dump_caps_bits(struct seq_file *s, struct ubase_dev *udev) +{ +#define CAP_FMT(name) "\tsupport_" #name ": %d\n" +#define PTRINT_CAP(name, func) seq_printf(s, CAP_FMT(name), func(udev)) + + PTRINT_CAP(ub_link, ubase_dev_ubl_supported); + PTRINT_CAP(ta_extdb_buffer_config, ubase_dev_ta_extdb_buf_supported); + PTRINT_CAP(ta_timer_buffer_config, ubase_dev_ta_timer_buf_supported); + PTRINT_CAP(err_handle, ubase_dev_err_handle_supported); + PTRINT_CAP(ctrlq, ubase_dev_ctrlq_supported); + PTRINT_CAP(eth_mac, ubase_dev_eth_mac_supported); + PTRINT_CAP(mac_stats, ubase_dev_mac_stats_supported); + PTRINT_CAP(prealloc, __ubase_dev_prealloc_supported); + PTRINT_CAP(udma, ubase_dev_udma_supported); + PTRINT_CAP(unic, ubase_dev_unic_supported); + PTRINT_CAP(uvb, ubase_dev_uvb_supported); + PTRINT_CAP(ip_over_urma, ubase_ip_over_urma_supported); + if (ubase_ip_over_urma_supported(udev)) + PTRINT_CAP(ip_over_urma_utp, ubase_ip_over_urma_utp_supported); + PTRINT_CAP(activate_proxy, ubase_activate_proxy_supported); + PTRINT_CAP(utp, ubase_utp_supported); +} + +static void ubase_dbg_dump_caps_info(struct seq_file *s, struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_dbg_common_caps_info { + const char *format; + u64 caps_info; + } ubase_common_caps_info[] = { + {"\tnum_ceq_vectors: %u\n", dev_caps->num_ceq_vectors}, + {"\tnum_aeq_vectors: %u\n", dev_caps->num_aeq_vectors}, + {"\tnum_misc_vectors: %u\n", dev_caps->num_misc_vectors}, + {"\taeqe_size: %u\n", dev_caps->aeqe_size}, + {"\tceqe_size: %u\n", dev_caps->ceqe_size}, + {"\taeqe_depth: %u\n", dev_caps->aeqe_depth}, + {"\tceqe_depth: %u\n", dev_caps->ceqe_depth}, + {"\ttotal_ue_num: %u\n", dev_caps->total_ue_num}, + {"\tta_extdb_buf_size: %llu\n", udev->ta_ctx.extdb_buf.size}, + {"\tta_timer_buf_size: %llu\n", udev->ta_ctx.timer_buf.size}, + {"\tpublic_jetty_cnt: %u\n", dev_caps->public_jetty_cnt}, + {"\tvl_num: %hhu\n", dev_caps->vl_num}, + {"\trsvd_jetty_cnt: %hu\n", dev_caps->rsvd_jetty_cnt}, + {"\tpacket_pattern_mode: %u\n", dev_caps->packet_pattern_mode}, + {"\tack_queue_num: %u\n", dev_caps->ack_queue_num}, + {"\toor_en: %u\n", dev_caps->oor_en}, + {"\treorder_queue_en: %u\n", dev_caps->reorder_queue_en}, + {"\ton_flight_size: %u\n", dev_caps->on_flight_size}, + {"\treorder_cap: %u\n", dev_caps->reorder_cap}, + {"\treorder_queue_shift: %u\n", dev_caps->reorder_queue_shift}, + {"\tat_times: %u\n", dev_caps->at_times}, + {"\tue_num: %u\n", dev_caps->ue_num}, + {"\tmac_stats_num: %u\n", dev_caps->mac_stats_num}, + {"\tlogic_port_bitmap: 0x%x\n", dev_caps->logic_port_bitmap}, + {"\tub_port_logic_id: %u\n", dev_caps->ub_port_logic_id}, + {"\tio_port_logic_id: %u\n", dev_caps->io_port_logic_id}, + {"\tio_port_id: %u\n", dev_caps->io_port_id}, + {"\tnl_port_id: %u\n", dev_caps->nl_port_id}, + {"\tchip_id: %u\n", dev_caps->chip_id}, + {"\tdie_id: %u\n", dev_caps->die_id}, + {"\tue_id: %u\n", dev_caps->ue_id}, + {"\tnl_id: %u\n", dev_caps->nl_id}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(ubase_common_caps_info); i++) + seq_printf(s, ubase_common_caps_info[i].format, + ubase_common_caps_info[i].caps_info); +} + +static void ubase_dbg_dump_common_caps(struct seq_file *s, struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + + ubase_dbg_dump_caps_info(s, udev); + + seq_puts(s, "\treq_vl:"); + ubase_dbg_dump_arr_info(s, dev_caps->req_vl, dev_caps->vl_num); + + seq_puts(s, "\tresp_vl:"); + ubase_dbg_dump_arr_info(s, dev_caps->resp_vl, dev_caps->vl_num); +} + +static void ubase_dbg_dump_adev_caps(struct seq_file *s, + struct ubase_adev_caps *caps) +{ + struct ubase_dbg_adev_caps_info { + const char *format; + u32 caps_info; + } ubase_adev_caps_info[] = { + {"\tjfs_max_cnt: %u\n", caps->jfs.max_cnt}, + {"\tjfs_reserved_cnt: %u\n", caps->jfs.reserved_cnt}, + {"\tjfs_depth: %u\n", caps->jfs.depth}, + {"\tjfr_max_cnt: %u\n", caps->jfr.max_cnt}, + {"\tjfr_reserved_cnt: %u\n", caps->jfr.reserved_cnt}, + {"\tjfr_depth: %u\n", caps->jfr.depth}, + {"\tjfc_max_cnt: %u\n", caps->jfc.max_cnt}, + {"\tjfc_reserved_cnt: %u\n", caps->jfc.reserved_cnt}, + {"\tjfc_depth: %u\n", caps->jfc.depth}, + {"\ttp_max_cnt: %u\n", caps->tp.max_cnt}, + {"\ttp_reserved_cnt: %u\n", caps->tp.reserved_cnt}, + {"\ttp_depth: %u\n", caps->tp.depth}, + {"\ttpg_max_cnt: %u\n", caps->tpg.max_cnt}, + {"\ttpg_reserved_cnt: %u\n", caps->tpg.reserved_cnt}, + {"\ttpg_depth: %u\n", caps->tpg.depth}, + {"\tcqe_size: %hu\n", caps->cqe_size}, + {"\tutp_port_bitmap: 0x%x\n", caps->utp_port_bitmap}, + {"\tjtg_max_cnt: %u\n", caps->jtg_max_cnt}, + {"\trc_max_cnt: %u\n", caps->rc_max_cnt}, + {"\trc_depth: %u\n", caps->rc_que_depth}, + {"\tccc_max_cnt: %u\n", caps->ccc_max_cnt}, + {"\tdest_addr_max_cnt: %u\n", caps->dest_addr_max_cnt}, + {"\tseid_upi_max_cnt: %u\n", caps->seid_upi_max_cnt}, + {"\ttpm_max_cnt: %u\n", caps->tpm_max_cnt}, + {"\tprealloc_mem_dma_len: %llu\n", caps->pmem.dma_len}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(ubase_adev_caps_info); i++) + seq_printf(s, ubase_adev_caps_info[i].format, + ubase_adev_caps_info[i].caps_info); +} + +static int ubase_dbg_dump_dev_caps(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_dev_caps *udev_caps = &udev->caps; + + seq_puts(s, "CAP_BITS:\n"); + ubase_dbg_dump_caps_bits(s, udev); + seq_puts(s, "\nCOMMON_CAPS:\n"); + ubase_dbg_dump_common_caps(s, udev); + + if (ubase_dev_pmu_supported(udev)) + return 0; + + seq_puts(s, "\nUNIC_CAPS:\n"); + ubase_dbg_dump_adev_caps(s, &udev_caps->unic_caps); + + if (ubase_dev_cdma_supported(udev)) + seq_puts(s, "\nCDMA_CAPS:\n"); + else + seq_puts(s, "\nUDMA_CAPS:\n"); + ubase_dbg_dump_adev_caps(s, &udev_caps->udma_caps); + + return 0; +} + +static int ubase_query_ubcl_config(struct ubase_dev *udev, u16 offset, + u16 is_query, u16 size, + struct ubase_ubcl_config_cmd *resp) +{ + struct ubase_ubcl_config_cmd req; + struct ubase_cmd_buf in, out; + int ret; + + memset(resp, 0, sizeof(*resp)); + memset(&req, 0, sizeof(req)); + req.offset = cpu_to_le16(offset); + req.size = cpu_to_le16(size); + req.is_query_size = cpu_to_le16(is_query); + + __ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_UBCL_CONFIG, true, + sizeof(req), &req); + __ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_UBCL_CONFIG, true, + sizeof(*resp), resp); + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret && ret != -EPERM) + ubase_err(udev, "failed to query UBCL_config, ret = %d.\n", ret); + + if (ret == -EPERM) + return -EOPNOTSUPP; + + return ret; +} + +static void ubase_dbg_fill_ubcl_content(struct ubase_ubcl_config_cmd *resp, + u32 *addr, struct seq_file *s) +{ + int i, j; + + for (i = 0; i < UBASE_UBCL_CFG_DATA_NUM; i += UBASE_UBCL_CFG_DATA_ALIGN) { + seq_printf(s, "%08X: ", (*addr * UBASE_UBCL_CFG_DATA_ALIGN)); + for (j = 0; j < UBASE_UBCL_CFG_DATA_ALIGN; j++) + seq_printf(s, "%08X ", resp->data[i + j]); + seq_puts(s, "\n"); + + *addr += UBASE_UBCL_CFG_DATA_ALIGN; + if ((i * sizeof(u32)) >= resp->size) + break; + } +} + +static int ubase_dbg_dump_ubcl_config(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_ubcl_config_cmd resp = {0}; + u16 read_size = sizeof(resp.data); + u16 offset = 0; + u16 total_size; + u32 addr = 0; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_ubcl_config(udev, offset, 1, 0, &resp); + if (ret) + return ret; + total_size = le16_to_cpu(resp.size); + + seq_puts(s, "UBCL_config:\n"); + seq_printf(s, "total_size: %u\n", total_size); + while (offset < total_size) { + read_size = min(read_size, total_size - offset); + ret = ubase_query_ubcl_config(udev, offset, 0, read_size, &resp); + if (ret) + return ret; + offset += le16_to_cpu(resp.size); + + ubase_dbg_fill_ubcl_content(&resp, &addr, s); + } + + return 0; +} + +static int ubase_dbg_dump_activate_record(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_activate_dev_stats *record; + u8 cnt = 1, stats_cnt; + u64 total, idx; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + record = &udev->stats.activate_record; + + mutex_lock(&record->lock); + + seq_puts(s, "current time : "); + ubase_dbg_format_time(ktime_get_real_seconds(), s); + seq_puts(s, "\n"); + seq_printf(s, "activate dev count : %llu\n", record->act_cnt); + seq_printf(s, "deactivate dev count : %llu\n", record->deact_cnt); + + total = record->act_cnt + record->deact_cnt; + if (!total) { + seq_puts(s, "activate dev change records : NA\n"); + mutex_unlock(&record->lock); + return 0; + } + + seq_puts(s, "activate dev change records :\n"); + seq_puts(s, "\tNo.\tTIME\t\t\t\tSTATUS\t\tRESULT\n"); + + stats_cnt = min(total, UBASE_ACT_STAT_MAX_NUM); + while (cnt <= stats_cnt) { + total--; + idx = total % UBASE_ACT_STAT_MAX_NUM; + seq_printf(s, "\t%-2d\t", cnt); + ubase_dbg_format_time(record->stats[idx].time, s); + seq_printf(s, "\t%s", record->stats[idx].activate ? + "activate" : "deactivate"); + seq_printf(s, "\t%d", record->stats[idx].result); + seq_puts(s, "\n"); + cnt++; + } + + mutex_unlock(&record->lock); + + return 0; +} + +static void ubase_dbg_fill_single_port(struct seq_file *s, + struct ubase_perf_stats_result *stats) +{ + int i; + + seq_printf(s, "\tport_id: %u\n", stats->port_id); + seq_printf(s, "\tport_tx_bw: %u(kbps)\n", le32_to_cpu(stats->tx_port_bw)); + seq_printf(s, "\tport_rx_bw: %u(kbps)\n", le32_to_cpu(stats->rx_port_bw)); + seq_puts(s, "\tvl tx_bw(kbps) rx_bw(kbps)\n"); + + for (i = 0; i < UBASE_STATS_MAX_VL_NUM; i++) { + seq_printf(s, "\t%-5d", i); + seq_printf(s, "%-21u", le32_to_cpu(stats->tx_vl_bw[i])); + seq_printf(s, "%-21u", le32_to_cpu(stats->rx_vl_bw[i])); + seq_puts(s, "\n"); + } + seq_puts(s, "\n"); +} + +static int ubase_dbg_dump_perf_stats_ub(struct seq_file *s, + struct ubase_dev *udev) +{ +#define UBASE_UB_PERF_STATS_PERIOD 10 +#define UBASE_QUERY_ALL_BITMAP 0 + + struct ubase_perf_stats_result *stats; + struct device *dev = udev->dev; + int ret, i; + + stats = devm_kcalloc(dev, UBASE_MAX_PORT_NUM, + sizeof(struct ubase_perf_stats_result), GFP_KERNEL); + if (!stats) + return -ENOMEM; + + ret = __ubase_perf_stats(udev, UBASE_QUERY_ALL_BITMAP, + UBASE_UB_PERF_STATS_PERIOD, stats, + UBASE_MAX_PORT_NUM); + if (ret) { + devm_kfree(dev, stats); + return ret; + } + + seq_printf(s, "perf_stats_period: %d(ms)\n", UBASE_UB_PERF_STATS_PERIOD); + seq_puts(s, "port bandwidth info:\n"); + + for (i = 0; i < UBASE_MAX_PORT_NUM; i++) { + if (!stats[i].valid) + break; + ubase_dbg_fill_single_port(s, &stats[i]); + } + + devm_kfree(dev, stats); + + return 0; +} + +static int ubase_dbg_dump_perf_stats(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + int ret = 0; + + if (ubase_dev_ubl_supported(udev)) + ret = ubase_dbg_dump_perf_stats_ub(s, udev); + + return ret; +} + +static int ubase_dbg_dump_prealloc_mem_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_prealloc_mem_info *pmem_info = &udev->pmem_info; + int status; + + status = test_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits); + + seq_printf(s, "status:%s\n", status ? "enabled" : "disabled"); + seq_printf(s, "comm_page_cnt:%u\n", pmem_info->comm.page_cnt); + seq_printf(s, "udma_page_cnt:%u\n", pmem_info->udma.page_cnt); + + return 0; +} + +static bool __ubase_dbg_dentry_support(struct device *dev, u32 property) +{ + struct ubase_dev *udev = dev_get_drvdata(dev); + + if (((property & UBASE_SUP_UNIC) && ubase_dev_unic_supported(udev)) || + ((property & UBASE_SUP_UDMA) && ubase_dev_udma_supported(udev)) || + ((property & UBASE_SUP_CDMA) && ubase_dev_cdma_supported(udev)) || + ((property & UBASE_SUP_PMU) && ubase_dev_pmu_supported(udev))) { + if (((property & UBASE_SUP_UBL) && ubase_dev_ubl_supported(udev)) || + ((property & UBASE_SUP_ETH) && ubase_dev_eth_mac_supported(udev))) + return true; + } + + return false; +} + +bool ubase_dbg_dentry_support(struct auxiliary_device *adev, u32 property) +{ + if (!adev) + return false; + + return __ubase_dbg_dentry_support(__ubase_get_udev_by_adev(adev)->dev, + property); +} +EXPORT_SYMBOL(ubase_dbg_dentry_support); + +static int __ubase_dbg_seq_file_init(struct device *dev, + struct ubase_dbg_dentry_info *dirs, + struct ubase_dbgfs *dbgfs, u32 idx) +{ + struct ubase_dbg_cmd_info *cmd_info = &dbgfs->cmd_info[idx]; + struct dentry *cur_dir; + + cur_dir = dirs[cmd_info->dentry_index].dentry; + if (!cmd_info->read_func) + return -EFAULT; + + debugfs_create_devm_seqfile(dev, cmd_info->name, cur_dir, + cmd_info->read_func); + + return 0; +} + +int ubase_dbg_seq_file_init(struct device *dev, + struct ubase_dbg_dentry_info *dirs, + struct ubase_dbgfs *dbgfs, u32 idx) +{ + if (!dev || !dirs || !dbgfs || !dbgfs->cmd_info) + return -EINVAL; + + return __ubase_dbg_seq_file_init(dev, dirs, dbgfs, idx); +} +EXPORT_SYMBOL(ubase_dbg_seq_file_init); + +static struct ubase_dbg_dentry_info ubase_dbg_dentry[] = { + { + .name = "context", + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + }, + { + .name = "qos", + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + }, + /* ue debugfs top-level directory, + * "dev_name" refers to the ue name + */ + { + .name = "dev_name", + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_PMU | + UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + }, +}; + +static struct ubase_dbg_cmd_info ubase_dbg_cmd[] = { + { + .name = "reset_info", + .dentry_index = UBASE_DBG_DENTRY_ROOT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_PMU | + UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_rst_info, + }, + { + .name = "aeq_context", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_aeq_context, + }, + { + .name = "ceq_context", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_ceq_context, + }, + { + .name = "caps_info", + .dentry_index = UBASE_DBG_DENTRY_ROOT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_PMU | + UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_dev_caps, + }, + { + .name = "UBCL_config", + .dentry_index = UBASE_DBG_DENTRY_ROOT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_ubcl_config, + }, + { + .name = "activate_record", + .dentry_index = UBASE_DBG_DENTRY_ROOT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_activate_record, + }, + { + .name = "tpg_context", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tpg_ctx, + }, + { + .name = "tp_context_hw", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tp_ctx_hw, + }, + { + .name = "tpg_context_hw", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tpg_ctx_hw, + }, + { + .name = "aeq_context_hw", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_aeq_ctx_hw, + }, + { + .name = "ceq_context_hw", + .dentry_index = UBASE_DBG_DENTRY_CONTEXT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_ceq_ctx_hw, + }, + { + .name = "sl_vl_map", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_sl_vl_map, + }, + { + .name = "udma_dscp_vl_map", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_udma_dscp_vl_map, + }, + { + .name = "ets_tc", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_ets_tc_info, + }, + { + .name = "ets_tcg", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_ets_tcg_info, + }, + { + .name = "ets_port", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_ets_port_info, + }, + { + .name = "perf_stats", + .dentry_index = UBASE_DBG_DENTRY_ROOT, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_perf_stats, + }, + { + .name = "rack_vl_bitmap", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_rack_vl_bitmap, + }, + { + .name = "adev_qos", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_adev_qos_info, + }, + { + .name = "fst_fvt_rqmt_info", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_fsv_fvt_rqmt, + }, + { + .name = "tm_queue", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tm_queue_info, + }, + { + .name = "tm_qset", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tm_qset_info, + }, + { + .name = "tm_pri", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tm_pri_info, + }, + { + .name = "tm_pg", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tm_pg_info, + }, + { + .name = "tm_port", + .dentry_index = UBASE_DBG_DENTRY_QOS, + .property = UBASE_SUP_URMA | UBASE_SUP_CDMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_tm_port_info, + }, + { + .name = "prealloc_mem_info", + .dentry_index = UBASE_DBG_DENTRY_ROOT, + .property = UBASE_SUP_URMA | UBASE_SUP_UBL_ETH, + .support = __ubase_dbg_dentry_support, + .init = __ubase_dbg_seq_file_init, + .read_func = ubase_dbg_dump_prealloc_mem_info, + }, +}; + +static int ubase_dbg_create_dir(struct device *dev, + struct ubase_dbg_dentry_info *dirs, u32 root_idx) +{ + u32 i; + + for (i = 0; i < root_idx; i++) { + if (!dirs[i].support(dev, dirs[i].property)) + continue; + + dirs[i].dentry = debugfs_create_dir(dirs[i].name, + dirs[root_idx].dentry); + if (IS_ERR(dirs[i].dentry)) { + dev_err(dev, "failed to create %s dir.\n", dirs[i].name); + return PTR_ERR(dirs[i].dentry); + } + } + + return 0; +} + +static int ubase_dbg_create_file(struct device *dev, struct ubase_dbgfs *dbgfs, + struct ubase_dbg_dentry_info *dirs) +{ + struct ubase_dbg_cmd_info *cmd_info; + int i, ret; + + cmd_info = dbgfs->cmd_info; + for (i = 0; i < dbgfs->cmd_info_size; i++) { + if (!cmd_info[i].support(dev, cmd_info[i].property)) + continue; + + ret = cmd_info[i].init(dev, dirs, dbgfs, i); + if (ret) { + dev_err(dev, "failed to init cmd %s, ret = %d.\n", + cmd_info[i].name, ret); + return ret; + } + } + + return 0; +} + +int ubase_dbg_create_dentry(struct device *dev, struct ubase_dbgfs *dbgfs, + struct ubase_dbg_dentry_info *dirs, u32 root_idx) +{ + int ret; + + if (!dev || !dbgfs || !dirs || !dbgfs->cmd_info) + return -EINVAL; + + ret = ubase_dbg_create_dir(dev, dirs, root_idx); + if (ret) { + dev_err(dev, + "failed to create ubase debugfs dirs, ret = %d.\n", ret); + return ret; + } + + ret = ubase_dbg_create_file(dev, dbgfs, dirs); + if (ret) + dev_err(dev, + "failed to create ubase debugfs files, ret = %d.\n", ret); + + return ret; +} +EXPORT_SYMBOL(ubase_dbg_create_dentry); + +int ubase_dbg_init(struct ubase_dev *udev) +{ + struct ubase_dbg_dentry_info dentry[UBASE_DBG_DENTRY_ROOT + 1] = {0}; + const char *name = dev_name(udev->dev); + struct device *dev = udev->dev; + int ret; + + udev->dbgfs.dentry = debugfs_create_dir(name, ubase_dbgfs_root); + if (IS_ERR(udev->dbgfs.dentry)) { + ubase_err(udev, "failed to create ubase debugfs root dir.\n"); + return PTR_ERR(udev->dbgfs.dentry); + } + + memcpy(dentry, ubase_dbg_dentry, sizeof(dentry)); + dentry[UBASE_DBG_DENTRY_ROOT].dentry = udev->dbgfs.dentry; + udev->dbgfs.cmd_info = ubase_dbg_cmd; + udev->dbgfs.cmd_info_size = ARRAY_SIZE(ubase_dbg_cmd); + + ret = ubase_dbg_create_dentry(dev, &udev->dbgfs, dentry, + ARRAY_SIZE(dentry) - 1); + if (ret) { + ubase_err(udev, + "failed to create ubase debugfs dentry, ret = %d.\n", + ret); + goto create_dentry_err; + } + + return 0; + +create_dentry_err: + debugfs_remove_recursive(udev->dbgfs.dentry); + + return ret; +} + +void ubase_dbg_uninit(struct ubase_dev *udev) +{ + debugfs_remove_recursive(udev->dbgfs.dentry); +} + +int ubase_dbg_register_debugfs(void) +{ +#define UBASE_DBGFS_ROOT "ubase" + + ubase_dbgfs_root = debugfs_create_dir(UBASE_DBGFS_ROOT, NULL); + if (IS_ERR(ubase_dbgfs_root)) + return PTR_ERR(ubase_dbgfs_root); + + return 0; +} + +void ubase_dbg_unregister_debugfs(void) +{ + debugfs_remove_recursive(ubase_dbgfs_root); +} + +struct dentry *ubase_diag_debugfs_root(struct auxiliary_device *adev) +{ + if (!adev) + return NULL; + + return __ubase_get_udev_by_adev(adev)->dbgfs.dentry; +} +EXPORT_SYMBOL(ubase_diag_debugfs_root); + +int ubase_dbg_format_time(time64_t time, struct seq_file *s) +{ +#define YEAR_OFFSET 1900 + const char week[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + const char mouth[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + struct tm t; + + if (!s) + return -EINVAL; + + time64_to_tm(time, 0, &t); + + seq_printf(s, "%s %s %02d %02d:%02d:%02d %ld", week[t.tm_wday], + mouth[t.tm_mon], t.tm_mday, t.tm_hour, t.tm_min, + t.tm_sec, t.tm_year + YEAR_OFFSET); + return 0; +} +EXPORT_SYMBOL(ubase_dbg_format_time); diff --git a/drivers/ub/ubase/debugfs/ubase_debugfs.h b/drivers/ub/ubase/debugfs/ubase_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..8ea5dfc1bdf813e583f4bca864bcebda5f9b3892 --- /dev/null +++ b/drivers/ub/ubase/debugfs/ubase_debugfs.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_DEBUGFS_H__ +#define __UBASE_DEBUGFS_H__ + +#include +#include +#include + +#include "ubase_dev.h" + +enum ubase_dbg_dentry_type { + UBASE_DBG_DENTRY_CONTEXT = 0, + UBASE_DBG_DENTRY_QOS, + /* must be the last entry. */ + UBASE_DBG_DENTRY_ROOT, +}; + +static inline void ubase_dbg_dump_arr_info(struct seq_file *s, u8 *arr, u8 arr_num) +{ + u8 i; + + for (i = 0; i < arr_num; i++) + seq_printf(s, " %u", arr[i]); + + seq_puts(s, "\n"); +} + +int ubase_dbg_init(struct ubase_dev *udev); +void ubase_dbg_uninit(struct ubase_dev *udev); +int ubase_dbg_register_debugfs(void); +void ubase_dbg_unregister_debugfs(void); + +#endif diff --git a/drivers/ub/ubase/debugfs/ubase_qos_debugfs.c b/drivers/ub/ubase/debugfs/ubase_qos_debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..91e05df180bb94ac0efc12a1b45f5aedc681aee9 --- /dev/null +++ b/drivers/ub/ubase/debugfs/ubase_qos_debugfs.c @@ -0,0 +1,703 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "ubase_cmd.h" +#include "ubase_ctrlq.h" +#include "ubase_debugfs.h" +#include "ubase_hw.h" +#include "ubase_mailbox.h" +#include "ubase_qos_debugfs.h" + +int ubase_dbg_dump_sl_vl_map(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + u8 i, sl_vl[UBASE_MAX_SL_NUM] = {0}; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_sl_vl_map(udev, sl_vl); + if (ret) + return ret; + + seq_puts(s, "sl sw_vl hw_vl\n"); + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + seq_printf(s, "%-4u", i); + seq_printf(s, "%-7u", udev->qos.ue_sl_vl[i]); + seq_printf(s, "%-7u", sl_vl[i]); + seq_puts(s, "\n"); + } + + return ret; +} + +int ubase_dbg_dump_udma_dscp_vl_map(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + int i; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + seq_puts(s, "dscp vl\n"); + for (i = 0; i < UBASE_MAX_DSCP; i++) { + seq_printf(s, "%-7d", i); + seq_printf(s, "%-4u", udev->qos.dscp_vl[i]); + seq_puts(s, "\n"); + } + + return 0; +} + +int ubase_dbg_dump_ets_tc_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_cfg_ets_vl_sch_cmd resp = {0}; + unsigned long vl_bitmap = 0; + struct ubase_caps *caps; + u32 port_bitmap; + int ret; + u8 i; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + caps = &udev->caps.dev_caps; + port_bitmap = 1 << caps->io_port_id; + for (i = 0; i < caps->vl_num; i++) { + vl_bitmap |= 1 << caps->req_vl[i]; + vl_bitmap |= 1 << caps->resp_vl[i]; + } + + ret = ubase_query_ets_tc(udev, port_bitmap, vl_bitmap, &resp); + if (ret) + return ret; + + seq_puts(s, "TC_ID SCH_MODE WEIGHT\n"); + vl_bitmap = le16_to_cpu(resp.vl_bitmap); + for (i = 0; i < UBASE_MAX_VL_NUM; i++) { + if (!test_bit(i, &vl_bitmap)) + continue; + + seq_printf(s, "%-7u", i); + seq_printf(s, "%-11s", resp.vl_bw[i] ? "dwrr" : "sp"); + seq_printf(s, "%-9u", resp.vl_bw[i]); + seq_puts(s, "\n"); + } + + return 0; +} + +int ubase_dbg_dump_ets_tcg_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_ets_tcg_cmd resp = {0}; + int ret; + u8 i; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_ets_tcg(udev, &resp); + if (ret) + return ret; + + seq_puts(s, "TCG_ID SCH_MODE WEIGHT TC_BITMAP RATE(Mbps) "); + seq_puts(s, "IR_B IR_U IR_S BS_B BS_S\n"); + for (i = 0; i < UBASE_MAX_TCG_NUM; i++) { + seq_printf(s, "%-8u", i); + seq_printf(s, "%-11s", resp.tcg_weight[i] ? "dwrr" : "sp"); + seq_printf(s, "%-9u", resp.tcg_weight[i]); + seq_printf(s, "0x%04x%-6s", le16_to_cpu(resp.tcg_tc_map[i]), ""); + seq_printf(s, "%-13u", le32_to_cpu(resp.tcg_info[i].rate)); + seq_printf(s, "%-7u", resp.tcg_info[i].ir_b); + seq_printf(s, "%-7u", resp.tcg_info[i].ir_u); + seq_printf(s, "%-7u", resp.tcg_info[i].ir_s); + seq_printf(s, "%-7u", resp.tcg_info[i].bs_b); + seq_printf(s, "%-7u", resp.tcg_info[i].bs_s); + seq_puts(s, "\n"); + } + + return 0; +} + +int ubase_dbg_dump_ets_port_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_ets_port_cmd resp = {0}; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_ets_port(udev, &resp); + if (ret) + return ret; + + seq_printf(s, "RATE(Mbps): %u ", le32_to_cpu(resp.port_info.rate)); + seq_printf(s, "IR_B: %u ", resp.port_info.ir_b); + seq_printf(s, "IR_U: %u ", resp.port_info.ir_u); + seq_printf(s, "IR_S: %u ", resp.port_info.ir_s); + seq_printf(s, "BS_B: %u ", resp.port_info.bs_b); + seq_printf(s, "BS_S: %u", resp.port_info.bs_s); + seq_puts(s, "\n"); + + return 0; +} + +int ubase_dbg_dump_rack_vl_bitmap(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_ctrlq_query_vl_resp resp = {0}; + struct ubase_ctrlq_query_vl_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + u16 vl_bitmap; + int ret = 0; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_QOS; + msg.opcode = UBASE_CTRLQ_OPC_QUERY_VL; + msg.need_resp = 1; + msg.is_resp = 0; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) { + ubase_err(udev, + "failed to send ctrlq msg when query vl, ret = %d.\n", ret); + return ret; + } + + vl_bitmap = le16_to_cpu(resp.vl_bitmap); + + seq_printf(s, "rack vl bitmap : 0x%x", vl_bitmap); + + return 0; +} + +static void ubase_dbg_dump_adev_vl_info(struct seq_file *s, + struct ubase_adev_qos *qos) +{ + seq_puts(s, "vl:"); + ubase_dbg_dump_arr_info(s, qos->vl, qos->vl_num); + + seq_puts(s, "tp_req_vl:"); + ubase_dbg_dump_arr_info(s, qos->tp_req_vl, qos->tp_vl_num); + + seq_puts(s, "ctp_req_vl:"); + ubase_dbg_dump_arr_info(s, qos->ctp_req_vl, qos->ctp_vl_num); + + seq_puts(s, "nic_vl:"); + ubase_dbg_dump_arr_info(s, qos->nic_vl, qos->nic_vl_num); +} + +static void ubase_dbg_dump_adev_sl_info(struct seq_file *s, + struct ubase_adev_qos *qos) +{ + seq_puts(s, "sl:"); + ubase_dbg_dump_arr_info(s, qos->sl, qos->sl_num); + + seq_puts(s, "tp_sl:"); + ubase_dbg_dump_arr_info(s, qos->tp_sl, qos->tp_sl_num); + + seq_puts(s, "ctp_sl:"); + ubase_dbg_dump_arr_info(s, qos->ctp_sl, qos->ctp_sl_num); + + seq_puts(s, "nic_sl:"); + ubase_dbg_dump_arr_info(s, qos->nic_sl, qos->nic_sl_num); +} + +int ubase_dbg_dump_adev_qos_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_adev_qos *qos = &udev->qos; + struct ubase_dbg_adev_qos_info { + const char *format; + u8 qos_info; + } adev_qos_info[] = { + {"vl_num: %u\n", qos->vl_num}, + {"tp_vl_num: %u\n", qos->tp_vl_num}, + {"ctp_vl_num: %u\n", qos->ctp_vl_num}, + {"tp_resp_vl_offset: %u\n", qos->tp_resp_vl_offset}, + {"ctp_resp_vl_offset: %u\n", qos->ctp_resp_vl_offset}, + {"sl_num: %u\n", qos->sl_num}, + {"tp_sl_num: %u\n", qos->tp_sl_num}, + {"ctp_sl_num: %u\n", qos->ctp_sl_num}, + {"nic_sl_num: %u\n", qos->nic_sl_num}, + {"nic_vl_num: %u\n", qos->nic_vl_num}, + {"ue_max_vl_id: %u\n", qos->ue_max_vl_id}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(adev_qos_info); i++) + seq_printf(s, adev_qos_info[i].format, adev_qos_info[i].qos_info); + + ubase_dbg_dump_adev_vl_info(s, qos); + ubase_dbg_dump_adev_sl_info(s, qos); + + return 0; +} + +static void ubase_dbg_fill_fst_fvt(struct seq_file *s, + struct ubase_query_fst_fvt_rqmt_cmd *resp) +{ + seq_puts(s, "\tFST:\n"); + seq_printf(s, "\t\tsl_queue_vl_num: %u\n", + le16_to_cpu(resp->sl_queue_vl_num)); + seq_printf(s, "\t\tsl_queue_start_qid: %u\n", + le16_to_cpu(resp->sl_queue_start_qid)); + seq_puts(s, "\tFVT:\n"); + seq_printf(s, "\t\tfvt_vl_size: %u\n", le16_to_cpu(resp->fvt_vl_size)); + seq_printf(s, "\t\tfvt_rqmt_offset: %u\n", + le16_to_cpu(resp->fvt_rqmt_offset)); +} + +static void ubase_dbg_fill_fst_revert(struct seq_file *s, + struct ubase_query_fst_fvt_rqmt_cmd *resp) +{ + u16 vl_num = min(UBASE_MAX_VL_NUM, le16_to_cpu(resp->sl_queue_vl_num)); + u16 j; + + seq_puts(s, "\tFST_REVERT:\n"); + seq_puts(s, "\t\tFST_IDX UE_IDX QUE_IDX VL_NUM\n"); + + for (j = 0; j < vl_num; j++) { + seq_puts(s, "\t\t"); + seq_printf(s, "%-9u", le16_to_cpu(resp->fstr_info[j].fst_idx)); + seq_printf(s, "%-10u", resp->fstr_info[j].queue_ue_num); + seq_printf(s, "%-10u", resp->fstr_info[j].queue_que_num); + seq_printf(s, "%-9u", le16_to_cpu(resp->fstr_info[j].queue_vl_num)); + seq_puts(s, "\n"); + } +} + +static void ubase_dbg_fill_rqmt(struct seq_file *s, + struct ubase_query_fst_fvt_rqmt_cmd *resp) +{ + u16 vl_size = min(UBASE_MAX_VL_NUM, le16_to_cpu(resp->fvt_vl_size)); + u16 j; + + seq_puts(s, "\tRQMT:\n"); + seq_puts(s, "\t\tFST_IDX QUE_IDX QUE_SHIFT\n"); + + for (j = 0; j < vl_size; j++) { + seq_puts(s, "\t\t"); + seq_printf(s, "%-9u", le16_to_cpu(resp->rqmt_info[j].fst_idx)); + seq_printf(s, "%-10u", + le16_to_cpu(resp->rqmt_info[j].start_queue_idx)); + seq_printf(s, "%-12u", + le16_to_cpu(resp->rqmt_info[j].queue_quantity_shift)); + seq_puts(s, "\n"); + } + + seq_puts(s, "\n"); +} + +static void ubase_dbg_fill_tbl_content(struct seq_file *s, + struct ubase_query_fst_fvt_rqmt_cmd *resp) +{ + ubase_dbg_fill_fst_fvt(s, resp); + + ubase_dbg_fill_fst_revert(s, resp); + + ubase_dbg_fill_rqmt(s, resp); +} + +int ubase_dbg_dump_fsv_fvt_rqmt(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_fst_fvt_rqmt_cmd resp = {0}; + struct ubase_ue_node *ue_node; + u16 ue_id; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + seq_puts(s, "current ue:\n"); + ret = ubase_query_fst_fvt_rqmt(udev, &resp, 0); + if (ret) + return ret; + + ubase_dbg_fill_tbl_content(s, &resp); + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry(ue_node, &udev->ue_list, list) { + ue_id = ue_node->bus_ue_id; + memset(&resp, 0, sizeof(resp)); + + seq_printf(s, "ue%u:\n", ue_id); + + ret = ubase_query_fst_fvt_rqmt(udev, &resp, ue_id); + if (ret) + goto out; + ubase_dbg_fill_tbl_content(s, &resp); + } + +out: + mutex_unlock(&udev->ue_list_lock); + return ret; +} + +static void ubase_dbg_fill_tm_queue_seq(struct seq_file *s, + struct ubase_query_tm_queue_cmd *resp) +{ + u16 bitmap = le16_to_cpu(resp->link_vld_bitmap); + u8 i; + + seq_puts(s, "QUEUE_ID QSET_ID VL_ID LINK_VLD_BITMAP\n"); + + for (i = 0; i < resp->queue_num; i++) { + seq_printf(s, "%-11u", resp->queue_id[i]); + seq_printf(s, "%-10u", resp->qset_id[i]); + seq_printf(s, "%-7u", resp->queue_vl[i]); + seq_printf(s, "%-16u", ((u32)bitmap >> i) & 1U); + seq_puts(s, "\n"); + } +} + +int ubase_dbg_dump_tm_queue_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_tm_queue_cmd resp = {0}; + struct ubase_ue_node *ue_node; + u16 ue_id = 0; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_tm_queue(udev, ue_id, &resp); + if (ret) + return ret; + + seq_printf(s, "current ue: %u\n", ue_id); + + ubase_dbg_fill_tm_queue_seq(s, &resp); + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry(ue_node, &udev->ue_list, list) { + ue_id = ue_node->bus_ue_id; + + memset(&resp, 0, sizeof(resp)); + ret = ubase_query_tm_queue(udev, ue_id, &resp); + if (ret) + goto out; + + seq_printf(s, "\ncurrent ue: %u\n", ue_id); + + ubase_dbg_fill_tm_queue_seq(s, &resp); + } + +out: + mutex_unlock(&udev->ue_list_lock); + return ret; +} + +static void ubase_dbg_fill_tm_qset_sharper_seq(struct ubase_query_tm_qset_cmd *resp, + struct seq_file *s, int qset_id) +{ + if (!resp->rate_limit_bypass) { + seq_printf(s, "%-7u", resp->ir_b[qset_id]); + seq_printf(s, "%-7u", resp->ir_u[qset_id]); + seq_printf(s, "%-7u", resp->ir_s[qset_id]); + seq_printf(s, "%-7u", resp->bs_b[qset_id]); + seq_printf(s, "%-7u", resp->bs_s[qset_id]); + seq_printf(s, "%-16u", le32_to_cpu(resp->rate[qset_id])); + return; + } + + seq_puts(s, "-- -- -- -- -- -- "); +} + +static void ubase_dbg_fill_tm_qset_seq(struct seq_file *s, + struct ubase_query_tm_qset_cmd *resp) +{ + u16 link_vld = le16_to_cpu(resp->qset_pri_link_vld); + u16 sch_mode = le16_to_cpu(resp->qset_sch_mode); + u8 i; + + seq_puts(s, "QSET_ID PRI_ID LINK_VLD_BITMAP SCH_MODE WEIGHT "); + seq_puts(s, "IR_B IR_U IR_S BS_B BS_S RATE(Mbps)\n"); + + for (i = 0; i < resp->qset_num; i++) { + seq_printf(s, "%-10u", resp->qset_id[i]); + seq_printf(s, "%-9u", resp->pri_id[i]); + seq_printf(s, "%-16u", ((u32)link_vld >> i) & 1U); + seq_printf(s, "%-11u", ((u32)sch_mode >> i) & 1U); + seq_printf(s, "%-9u", resp->qset_weight[i]); + + ubase_dbg_fill_tm_qset_sharper_seq(resp, s, i); + seq_puts(s, "\n"); + } +} + +int ubase_dbg_dump_tm_qset_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_tm_qset_cmd resp = {0}; + struct ubase_ue_node *ue_node; + u16 ue_id = 0; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_tm_qset(udev, ue_id, &resp); + if (ret) + return ret; + + seq_printf(s, "current ue: %u\n", ue_id); + + ubase_dbg_fill_tm_qset_seq(s, &resp); + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry(ue_node, &udev->ue_list, list) { + ue_id = ue_node->bus_ue_id; + + memset(&resp, 0, sizeof(resp)); + ret = ubase_query_tm_qset(udev, ue_id, &resp); + if (ret) + goto out; + + seq_printf(s, "\ncurrent ue: %u\n", ue_id); + + ubase_dbg_fill_tm_qset_seq(s, &resp); + } + +out: + mutex_unlock(&udev->ue_list_lock); + return ret; +} + +static void ubase_dbg_fill_tm_pri_csharper_seq(struct ubase_query_tm_pri_cmd *resp, + struct seq_file *s) +{ + if (!resp->c_rate_limit_bypass) { + seq_printf(s, "%-9u", resp->c_ir_b); + seq_printf(s, "%-9u", resp->c_ir_u); + seq_printf(s, "%-9u", resp->c_ir_s); + seq_printf(s, "%-9u", resp->c_bs_b); + seq_printf(s, "%-9u", resp->c_bs_s); + seq_printf(s, "%-15u", le32_to_cpu(resp->c_rate)); + return; + } + + seq_puts(s, "-- -- -- -- -- -- "); +} + +static void ubase_dbg_fill_tm_pri_psharper_seq(struct ubase_query_tm_pri_cmd *resp, + struct seq_file *s) +{ + if (!resp->p_rate_limit_bypass) { + seq_printf(s, "%-9u", resp->p_ir_b); + seq_printf(s, "%-9u", resp->p_ir_u); + seq_printf(s, "%-9u", resp->p_ir_s); + seq_printf(s, "%-9u", resp->p_bs_b); + seq_printf(s, "%-9u", resp->p_bs_s); + seq_printf(s, "%-15u", le32_to_cpu(resp->p_rate)); + return; + } + + seq_puts(s, "-- -- -- -- -- -- "); +} + +static void ubase_dbg_fill_tm_pri_seq(struct seq_file *s, + struct ubase_query_tm_pri_cmd *resp) +{ + seq_puts(s, "PRI_ID PG_ID SCH_MODE WEIGHT "); + seq_puts(s, "C_IR_B C_IR_U C_IR_S C_BS_B C_BS_S C_RATE(Mbps) "); + seq_puts(s, "P_IR_B P_IR_U P_IR_S P_BS_B P_BS_S P_RATE(Mbps)\n"); + + seq_printf(s, "%-9u", resp->pri_id); + seq_printf(s, "%-8u", resp->pg_id); + seq_printf(s, "%-10u", resp->pri_sch_mode); + seq_printf(s, "%-9u", resp->pri_weight); + + ubase_dbg_fill_tm_pri_csharper_seq(resp, s); + + ubase_dbg_fill_tm_pri_psharper_seq(resp, s); + seq_puts(s, "\n"); +} + +int ubase_dbg_dump_tm_pri_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_tm_pri_cmd resp = {0}; + struct ubase_ue_node *ue_node; + u16 ue_id = 0; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_tm_pri(udev, ue_id, &resp); + if (ret) + return ret; + + seq_printf(s, "current ue: %u\n", ue_id); + + ubase_dbg_fill_tm_pri_seq(s, &resp); + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry(ue_node, &udev->ue_list, list) { + ue_id = ue_node->bus_ue_id; + + memset(&resp, 0, sizeof(resp)); + ret = ubase_query_tm_pri(udev, ue_id, &resp); + if (ret) + goto out; + + seq_printf(s, "\ncurrent ue: %u\n", ue_id); + + ubase_dbg_fill_tm_pri_seq(s, &resp); + } + +out: + mutex_unlock(&udev->ue_list_lock); + return ret; +} + +static void ubase_dbg_fill_tm_pg_csharper_seq(struct ubase_query_tm_pg_cmd *resp, + struct seq_file *s) +{ + if (!resp->c_rate_limit_bypass) { + seq_printf(s, "%-9u", resp->c_ir_b); + seq_printf(s, "%-9u", resp->c_ir_u); + seq_printf(s, "%-9u", resp->c_ir_s); + seq_printf(s, "%-9u", resp->c_bs_b); + seq_printf(s, "%-9u", resp->c_bs_s); + seq_printf(s, "%-15u", le32_to_cpu(resp->c_rate)); + return; + } + + seq_puts(s, "-- -- -- -- -- -- "); +} + +static void ubase_dbg_fill_tm_pg_psharper_seq(struct ubase_query_tm_pg_cmd *resp, + struct seq_file *s) +{ + if (!resp->p_rate_limit_bypass) { + seq_printf(s, "%-9u", resp->p_ir_b); + seq_printf(s, "%-9u", resp->p_ir_u); + seq_printf(s, "%-9u", resp->p_ir_s); + seq_printf(s, "%-9u", resp->p_bs_b); + seq_printf(s, "%-9u", resp->p_bs_s); + seq_printf(s, "%-15u", le32_to_cpu(resp->p_rate)); + return; + } + + seq_puts(s, "-- -- -- -- -- -- "); +} + +static void ubase_dbg_fill_tm_pg_seq(struct seq_file *s, + struct ubase_query_tm_pg_cmd *resp) +{ + seq_puts(s, "PG_ID SCH_MODE WEIGHT "); + seq_puts(s, "C_IR_B C_IR_U C_IR_S C_BS_B C_BS_S C_RATE(Mbps) "); + seq_puts(s, "P_IR_B P_IR_U P_IR_S P_BS_B P_BS_S P_RATE(Mbps)\n"); + + seq_printf(s, "%-8u", resp->pg_id); + seq_printf(s, "%-10u", resp->pg_sch_mode); + seq_printf(s, "%-9u", resp->pg_weight); + + ubase_dbg_fill_tm_pg_csharper_seq(resp, s); + + ubase_dbg_fill_tm_pg_psharper_seq(resp, s); + seq_puts(s, "\n"); +} + +int ubase_dbg_dump_tm_pg_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_tm_pg_cmd resp = {0}; + struct ubase_ue_node *ue_node; + u16 ue_id = 0; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_tm_pg(udev, ue_id, &resp); + if (ret) + return ret; + + seq_printf(s, "current ue: %u\n", ue_id); + + ubase_dbg_fill_tm_pg_seq(s, &resp); + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry(ue_node, &udev->ue_list, list) { + ue_id = ue_node->bus_ue_id; + + memset(&resp, 0, sizeof(resp)); + ret = ubase_query_tm_pg(udev, ue_id, &resp); + if (ret) + goto out; + + seq_printf(s, "\ncurrent ue: %u\n", ue_id); + + ubase_dbg_fill_tm_pg_seq(s, &resp); + } + +out: + mutex_unlock(&udev->ue_list_lock); + return ret; +} + +static void ubase_dbg_fill_tm_port_sharper_seq(struct ubase_query_tm_port_cmd *resp, + struct seq_file *s) +{ + if (!resp->rate_limit_bypass) { + seq_printf(s, "%-7u", resp->ir_b); + seq_printf(s, "%-7u", resp->ir_u); + seq_printf(s, "%-7u", resp->ir_s); + seq_printf(s, "%-7u", resp->bs_b); + seq_printf(s, "%-7u", resp->bs_s); + seq_printf(s, "%-13u", le32_to_cpu(resp->rate)); + return; + } + + seq_puts(s, "-- -- -- -- -- -- "); +} + +int ubase_dbg_dump_tm_port_info(struct seq_file *s, void *data) +{ + struct ubase_dev *udev = dev_get_drvdata(s->private); + struct ubase_query_tm_port_cmd resp = {0}; + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + ret = ubase_query_tm_port(udev, &resp); + if (ret) + return ret; + + seq_puts(s, "IR_B IR_U IR_S BS_B BS_S RATE(Mbps)\n"); + + ubase_dbg_fill_tm_port_sharper_seq(&resp, s); + seq_puts(s, "\n"); + + return 0; +} diff --git a/drivers/ub/ubase/debugfs/ubase_qos_debugfs.h b/drivers/ub/ubase/debugfs/ubase_qos_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..e44b4cacd21e1b589bd0b4639fb65443dd085ce6 --- /dev/null +++ b/drivers/ub/ubase/debugfs/ubase_qos_debugfs.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_QOS_DEBUGFS_H__ +#define __UBASE_QOS_DEBUGFS_H__ + +struct device; + +int ubase_dbg_dump_sl_vl_map(struct seq_file *s, void *data); +int ubase_dbg_dump_udma_dscp_vl_map(struct seq_file *s, void *data); +int ubase_dbg_dump_ets_tc_info(struct seq_file *s, void *data); +int ubase_dbg_dump_ets_tcg_info(struct seq_file *s, void *data); +int ubase_dbg_dump_ets_port_info(struct seq_file *s, void *data); +int ubase_dbg_dump_rack_vl_bitmap(struct seq_file *s, void *data); +int ubase_dbg_dump_adev_qos_info(struct seq_file *s, void *data); +int ubase_dbg_dump_fsv_fvt_rqmt(struct seq_file *s, void *data); +int ubase_dbg_dump_tm_queue_info(struct seq_file *s, void *data); +int ubase_dbg_dump_tm_qset_info(struct seq_file *s, void *data); +int ubase_dbg_dump_tm_pri_info(struct seq_file *s, void *data); +int ubase_dbg_dump_tm_pg_info(struct seq_file *s, void *data); +int ubase_dbg_dump_tm_port_info(struct seq_file *s, void *data); + +#endif diff --git a/drivers/ub/ubase/ubase.h b/drivers/ub/ubase/ubase.h new file mode 100644 index 0000000000000000000000000000000000000000..e4e1517a061c22a3eddf3d885af55251e672b4ba --- /dev/null +++ b/drivers/ub/ubase/ubase.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_H__ +#define __UBASE_H__ + +#include +#include + +#define UBASE_CAP_LEN 3 +#define UBASE_MAX_TCG_NUM (4) +#define UBASE_PMEM_PAGE_SIZE (2 * 1024 * 1024UL) /* 2MB */ + +enum ubase_service_state { + UBASE_STATE_CRQ_SERVICE_SCHED, + UBASE_STATE_CRQ_HANDLING, + UBASE_SERVICE_STATE_ERR_SCHED, + UBASE_SERVICE_STATE_RESET_SCHED, + UBASE_STATE_CTRLQ_SERVICE_SCHED, + UBASE_STATE_CTRLQ_HANDLING, + UBASE_STATE_ARQ_SERVICE_SCHED, + UBASE_STATE_ARQ_HANDLING, +}; + +struct ubase_delay_work { + struct delayed_work service_task; + unsigned long state; +}; + +enum { + UBASE_SUPPORT_UBL_B = 0, + UBASE_SUPPORT_TA_EXTDB_BUF_B = 2, + UBASE_SUPPORT_TA_TIMER_BUF_B = 3, + UBASE_SUPPORT_ERR_HANDLE_B = 4, + UBASE_SUPPORT_CTRLQ_B = 5, + UBASE_SUPPORT_ETH_MAC_B = 6, + UBASE_SUPPORT_MAC_STATS_B = 10, + UBASE_SUPPORT_PRE_ALLOC_B = 13, + UBASE_SUPPORT_UDMA_DISABLE_B = 14, + UBASE_SUPPORT_UNIC_DISABLE_B = 15, + UBASE_SUPPORT_UVB_B = 16, + UBASE_SUPPORT_IP_OVER_URMA_B = 17, + UBASE_SUPPORT_IP_OVER_URMA_UTP_B = 18, + UBASE_SUPPORT_ACTIVATE_PROXY_B = 19, + UBASE_SUPPORT_UTP_B = 20, + + /* must be last entry and it should <= UBASE_CAP_LEN * 32 */ + UBASE_SUPPORT_MASK_NBITS +}; + +#define ubase_get_cap_bit(udev, nr) \ + test_bit(nr, (unsigned long *)((udev)->cap_bits)) + +static inline void ubase_write_reg(void __iomem *base, u32 reg, u32 value) +{ + writel(value, base + reg); +} + +static inline u32 ubase_read_reg(u8 __iomem *base, u32 reg) +{ + u8 __iomem *reg_addr = READ_ONCE(base); + + return readl(reg_addr + reg); +} + +#define ubase_write_dev(a, reg, value) \ + ubase_write_reg((a)->io_base.addr, reg, value) +#define ubase_read_dev(a, reg) \ + ubase_read_reg((a)->io_base.addr, reg) + +#define ubase_addr_gen(addr_h, addr_l) ((u64)(addr_h) << 32 | (addr_l)) + +#endif diff --git a/drivers/ub/ubase/ubase_arq.c b/drivers/ub/ubase/ubase_arq.c new file mode 100644 index 0000000000000000000000000000000000000000..10735a68d7aa934148b75ba7245809cbe6178c74 --- /dev/null +++ b/drivers/ub/ubase/ubase_arq.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include "ubase_arq.h" +#include "ubase_cmd.h" + +void ubase_arq_init(struct ubase_dev *udev) +{ + struct ubase_arq_msg_ring *arq = &udev->arq; + + arq->ci = 0; + arq->pi = 0; + atomic_set(&arq->count, 0); +} + +void ubase_arq_uninit(struct ubase_dev *udev) +{ + struct ubase_arq_msg_ring *arq = &udev->arq; + int i; + + for (i = 0; i < MAX_ARQ_MSG_NUM; i++) { + kfree(arq->msg[i].data); + arq->msg[i].data = NULL; + } +} + +static void ubase_send_activate_resp(struct ubase_dev *udev, __le16 bus_ue_id, + __le16 msn, int result) +{ + struct ubase_activate_resp resp = {0}; + struct ubase_cmd_buf in; + int ret; + + resp.bus_ue_id = bus_ue_id; + resp.msn = msn; + resp.result = result < 0 ? (u8)(-result) : (u8)result; + ubase_fill_inout_buf(&in, UBASE_OPC_ACTIVATE_RESP, false, sizeof(resp), + &resp); + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, + "failed to send activate dev resp, ue id = %u, msn = %u, ret = %d.\n", + le16_to_cpu(bus_ue_id), le16_to_cpu(msn), ret); +} + +static int ubase_activate_ue(struct ubase_dev *udev, struct ub_entity *ue, + u16 msn, u16 bus_ue_id) +{ + int ret; + + ret = ub_activate_entity(ue, bus_ue_id); + if (ret == -EBUSY) + ret = ubase_activate_handler(udev, bus_ue_id); + if (ret) + dev_err(udev->dev, + "failed to activate ue dev, ue id = %u, msn = %u, ret = %d.\n", + bus_ue_id, msn, ret); + + return ret; +} + +static int ubase_deactivate_ue(struct ubase_dev *udev, struct ub_entity *ue, + u16 msn, u16 bus_ue_id) +{ + int ret; + + ret = ub_deactivate_entity(ue, bus_ue_id); + if (ret == -EBUSY) + ret = ubase_deactivate_handler(udev, bus_ue_id); + if (ret) + dev_err(udev->dev, + "failed to deactivate ue dev, ue id=%u, msn=%u, ret=%d.\n", + bus_ue_id, msn, ret); + + return ret; +} + +static void ubase_handle_activate_req(struct ubase_dev *udev, void *data, + u32 len) +{ + struct ubase_activate_req *req = data; + u16 bus_ue_id = le16_to_cpu(req->bus_ue_id); + u16 msn = le16_to_cpu(req->msn); + struct ub_entity *ue; + int ret; + + if (!ubase_activate_proxy_supported(udev)) + return; + + ue = container_of(udev->dev, struct ub_entity, dev); + if (req->activate) + ret = ubase_activate_ue(udev, ue, msn, bus_ue_id); + else + ret = ubase_deactivate_ue(udev, ue, msn, bus_ue_id); + + ubase_send_activate_resp(udev, req->bus_ue_id, req->msn, ret); +} + +struct ubase_arq_event { + u16 opcode; + u16 len; + void (*arq_handler)(struct ubase_dev *udev, void *data, u32 len); +} ubase_arq_map[] = { + { + .opcode = UBASE_OPC_ACTIVATE_REQ, + .len = sizeof(struct ubase_activate_req), + .arq_handler = ubase_handle_activate_req, + }, +}; + +bool ubase_is_arq_msg(u16 opcode) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(ubase_arq_map); i++) { + if (ubase_arq_map[i].opcode == opcode) + return true; + } + + return false; +} + +static struct ubase_arq_event *ubase_get_arq_event(u16 opcode, u32 data_len) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(ubase_arq_map); i++) { + if (ubase_arq_map[i].opcode == opcode) + return data_len != ubase_arq_map[i].len ? + NULL : &ubase_arq_map[i]; + } + + return NULL; +} + +void ubase_cmd_arq_handler(struct ubase_delay_work *ubase_work) +{ + struct ubase_dev *udev = container_of(ubase_work, struct ubase_dev, + arq_service_task); + struct ubase_arq_msg_ring *arq = &udev->arq; + struct ubase_arq_event *event; + struct ubase_arq_msg *msg; + + if (!test_and_clear_bit(UBASE_STATE_ARQ_SERVICE_SCHED, + &udev->arq_service_task.state) || + test_and_set_bit(UBASE_STATE_ARQ_HANDLING, + &udev->arq_service_task.state)) + return; + + while (atomic_read(&arq->count) > 0) { + msg = &arq->msg[arq->ci]; + event = ubase_get_arq_event(msg->opcode, msg->data_len); + if (event) + event->arq_handler(udev, msg->data, msg->data_len); + kfree(msg->data); + msg->data = NULL; + + arq->ci = (arq->ci + 1) % MAX_ARQ_MSG_NUM; + atomic_dec(&arq->count); + } + + clear_bit(UBASE_STATE_ARQ_HANDLING, &udev->arq_service_task.state); +} + +static void ubase_arq_task_schedule(struct ubase_dev *udev) +{ + if (!test_and_set_bit(UBASE_STATE_ARQ_SERVICE_SCHED, + &udev->arq_service_task.state)) + mod_delayed_work(udev->ubase_arq_wq, + &udev->arq_service_task.service_task, 0); +} + +void ubase_add_to_arq(struct ubase_dev *udev, u16 opcode, void *msg_data, + u32 msg_data_len) +{ + struct ubase_arq_msg_ring *arq = &udev->arq; + + if (atomic_read(&arq->count) >= MAX_ARQ_MSG_NUM) { + ubase_warn(udev, + "arq queue full, drop opcode = 0x%x.\n", opcode); + return; + } + + arq->msg[arq->pi].data = kzalloc(msg_data_len, GFP_KERNEL); + if (!arq->msg[arq->pi].data) { + ubase_err(udev, + "failed to alloc arq msg data, opcode = 0x%x.\n", + opcode); + return; + } + + memcpy(arq->msg[arq->pi].data, msg_data, msg_data_len); + arq->msg[arq->pi].data_len = msg_data_len; + arq->msg[arq->pi].opcode = opcode; + arq->pi = (arq->pi + 1) % MAX_ARQ_MSG_NUM; + atomic_inc(&arq->count); + + ubase_arq_task_schedule(udev); +} diff --git a/drivers/ub/ubase/ubase_arq.h b/drivers/ub/ubase/ubase_arq.h new file mode 100644 index 0000000000000000000000000000000000000000..1998192b424e795b276530d48a6d6c9cb295aaa4 --- /dev/null +++ b/drivers/ub/ubase/ubase_arq.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_ARQ_H__ +#define __UBASE_ARQ_H__ + +#include + +#include "ubase_dev.h" + +void ubase_arq_init(struct ubase_dev *udev); +void ubase_arq_uninit(struct ubase_dev *udev); +void ubase_add_to_arq(struct ubase_dev *udev, u16 opcode, void *msg_data, + u32 msg_data_len); +bool ubase_is_arq_msg(u16 opcode); +void ubase_cmd_arq_handler(struct ubase_delay_work *ubase_work); + +#endif diff --git a/drivers/ub/ubase/ubase_cmd.c b/drivers/ub/ubase/ubase_cmd.c new file mode 100644 index 0000000000000000000000000000000000000000..4526f92ac1177d5e24409ae475a52395e7be5fea --- /dev/null +++ b/drivers/ub/ubase/ubase_cmd.c @@ -0,0 +1,1000 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "ubase_cmd.h" +#include "ubase_arq.h" +#include "ubase_hw.h" + +/* When use tracepoint, must define "CREATE_TRACE_POINTS" before include the + * trace header file. + * If many source file need include the same header file, the + * "CREATE_TRACE_POINTS" only define once. + */ +#define CREATE_TRACE_POINTS +#include "ubase_trace.h" + +static int ubase_alloc_cmd_queue(struct ubase_dev *udev, + struct ubase_cmdq_ring *ring) +{ + size_t size = ring->desc_num * sizeof(struct ubase_cmdq_desc); + + ring->desc = dma_alloc_coherent(udev->dev, size, &ring->desc_dma_addr, + GFP_KERNEL); + if (!ring->desc) { + ubase_err(udev, "failed to alloc cmdq dma addr.\n"); + return -ENOMEM; + } + + return 0; +} + +static void ubase_free_cmd_queue(struct ubase_dev *udev, + struct ubase_cmdq_ring *ring) +{ + size_t size = ring->desc_num * sizeof(struct ubase_cmdq_desc); + + if (!ring->desc) + return; + + dma_free_coherent(udev->dev, size, ring->desc, ring->desc_dma_addr); + ring->desc = NULL; +} + +static int ubase_cmd_queue_init(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + int ret; + + spin_lock_init(&csq->lock); + spin_lock_init(&crq->lock); + + csq->pi = 0; + csq->ci = 0; + crq->pi = 0; + crq->ci = 0; + csq->desc_num = UBASE_CMDQ_DESC_NUM; + crq->desc_num = UBASE_CMDQ_DESC_NUM; + csq->tx_timeout = UBASE_CMDQ_TX_TIMEOUT; + + ret = ubase_alloc_cmd_queue(udev, csq); + if (ret) { + ubase_err(udev, "failed to alloc csq, ret = %d.\n", ret); + return ret; + } + + ret = ubase_alloc_cmd_queue(udev, crq); + if (ret) { + ubase_err(udev, "failed to alloc crq, ret = %d.\n", ret); + goto err_csq; + } + + return 0; + +err_csq: + ubase_free_cmd_queue(udev, csq); + return ret; +} + +static void ubase_cmd_queue_uninit(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + + ubase_free_cmd_queue(udev, csq); + ubase_free_cmd_queue(udev, crq); +} + +static void ubase_cmd_init_regs(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + u32 reg_val; + + spin_lock_bh(&csq->lock); + spin_lock(&crq->lock); + + /* csq init */ + ubase_write_dev(&udev->hw, UBASE_CSQ_BASEADDR_L_REG, + lower_32_bits(csq->desc_dma_addr)); + ubase_write_dev(&udev->hw, UBASE_CSQ_BASEADDR_H_REG, + upper_32_bits(csq->desc_dma_addr)); + reg_val = csq->desc_num >> UBASE_CMDQ_DESC_NUM_S; + ubase_write_dev(&udev->hw, UBASE_CSQ_DEPTH_REG, reg_val); + ubase_write_dev(&udev->hw, UBASE_CSQ_HEAD_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CSQ_TAIL_REG, 0); + + /* crq init */ + ubase_write_dev(&udev->hw, UBASE_CRQ_BASEADDR_L_REG, + lower_32_bits(crq->desc_dma_addr)); + ubase_write_dev(&udev->hw, UBASE_CRQ_BASEADDR_H_REG, + upper_32_bits(crq->desc_dma_addr)); + reg_val = crq->desc_num >> UBASE_CMDQ_DESC_NUM_S; + ubase_write_dev(&udev->hw, UBASE_CRQ_DEPTH_REG, reg_val); + ubase_write_dev(&udev->hw, UBASE_CRQ_HEAD_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CRQ_TAIL_REG, 0); + + spin_unlock(&crq->lock); + spin_unlock_bh(&csq->lock); +} + +static void ubase_cmd_uninit_regs(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + + spin_lock_bh(&csq->lock); + spin_lock(&crq->lock); + + ubase_write_dev(&udev->hw, UBASE_CSQ_BASEADDR_L_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CSQ_BASEADDR_H_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CSQ_DEPTH_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CSQ_HEAD_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CSQ_TAIL_REG, 0); + + ubase_write_dev(&udev->hw, UBASE_CRQ_BASEADDR_L_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CRQ_BASEADDR_H_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CRQ_DEPTH_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CRQ_HEAD_REG, 0); + ubase_write_dev(&udev->hw, UBASE_CRQ_TAIL_REG, 0); + + spin_unlock(&crq->lock); + spin_unlock_bh(&csq->lock); +} + +static void ubase_write_desc_to_cmdq(struct ubase_dev *udev, + struct ubase_cmdq_desc *desc, int num) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + struct ubase_cmdq_desc *desc_to_use; + int cnt = 0; + + while (cnt < num) { + desc_to_use = &csq->desc[csq->pi]; + *desc_to_use = desc[cnt]; + trace_ubase_csq_tx(udev->dev, cnt, csq->pi, csq->ci, desc); + (csq->pi)++; + if (csq->pi >= csq->desc_num) + csq->pi = 0; + cnt++; + } + + ubase_write_dev(&udev->hw, UBASE_CSQ_TAIL_REG, csq->pi); +} + +static int ubase_remain_cmdq_space(struct ubase_cmdq_ring *csq) +{ + u32 used = (csq->pi - csq->ci + csq->desc_num) % csq->desc_num; + + return csq->desc_num - used - 1; +} + +static bool ubase_wait_for_resp(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + u32 timeout = 0; + u32 ci; + + do { + ci = ubase_read_dev(&udev->hw, UBASE_CSQ_HEAD_REG); + if (ci == csq->pi) + return true; + udelay(1); + timeout++; + } while (timeout < csq->tx_timeout); + + return false; +} + +static int ubase_get_cmd_result(struct ubase_dev *udev, + struct ubase_cmdq_desc *desc, + int num, u32 sw_pi) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + u32 pi = sw_pi; + int handle; + u16 ret; + + for (handle = 0; handle < num; handle++) { + desc[handle] = csq->desc[pi]; + trace_ubase_csq_rx(udev->dev, handle, pi, csq->ci, desc); + pi++; + if (pi >= csq->desc_num) + pi = 0; + } + + if (desc->flag & UBASE_CMD_FLAG_OUT) + ret = le16_to_cpu(desc->ret); + else + ret = ETIMEDOUT; + + return -ret; +} + +static int ubase_csq_data_is_valid(struct ubase_dev *udev, u32 hw_ci) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + u32 sw_ci = csq->ci; + u32 sw_pi = csq->pi; + + if (sw_pi > sw_ci) + return hw_ci >= sw_ci && hw_ci <= sw_pi; + + return hw_ci >= sw_ci || hw_ci <= sw_pi; +} + +static int ubase_csq_clean(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + s64 hw_ci; + int clean; + + hw_ci = ubase_read_dev(&udev->hw, UBASE_CSQ_HEAD_REG); + /* Make sure head is ready before touch any data */ + rmb(); + if (!ubase_csq_data_is_valid(udev, hw_ci)) { + ubase_warn(udev, + "the cmd head is incorrect! cmd head = (%lld, %u-%u).\n", + hw_ci, csq->pi, csq->ci); + ubase_warn(udev, + "any further commands to the firmware are disabled!\n"); + set_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state); + ubase_warn(udev, + "the firmware watchdog is expected to reset soon!\n"); + return -EIO; + } + + clean = (int)((hw_ci - (s64)(csq->ci) + (s64)(csq->desc_num)) % + (s64)(csq->desc_num)); + csq->ci = hw_ci; + + return clean; +} + +int ubase_send_cmd(struct ubase_dev *udev, + struct ubase_cmdq_desc *desc, int num) +{ + struct ubase_cmdq_ring *csq = &udev->hw.cmdq.csq; + bool is_completed = false; + int cleaned; + u32 sw_pi; + int ret; + + spin_lock_bh(&csq->lock); + if (test_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state)) { + ret = -EBUSY; + goto err_unlock; + } + + if (num > ubase_remain_cmdq_space(csq)) { + csq->ci = ubase_read_dev(&udev->hw, UBASE_CSQ_HEAD_REG); + ubase_warn(udev, + "the requested space exceeds the remaining space.\n"); + ret = -EBUSY; + goto err_unlock; + } + + /** + * Record the location of desc in the ring for this time + * which will be use for hardware to write back + */ + sw_pi = csq->pi; + + ubase_write_desc_to_cmdq(udev, desc, num); + is_completed = ubase_wait_for_resp(udev); + if (!is_completed) { + ret = -EBADE; + goto err_clr_cmdq; + } + ret = ubase_get_cmd_result(udev, desc, num, sw_pi); + +err_clr_cmdq: + cleaned = ubase_csq_clean(udev); + if (cleaned < 0) + ret = cleaned; + else if (cleaned != num) + ubase_warn(udev, + "cleaned %dBD, need to clean %dBD.\n", cleaned, num); +err_unlock: + spin_unlock_bh(&csq->lock); + + return ret; +} + +static int ubase_cmd_query_version(struct ubase_dev *udev, u32 *fw_version) +{ + struct ubase_query_version_cmd *resp; + struct ubase_cmdq_desc desc; + int ret; + + ubase_cmd_setup_basic_desc(&desc, UBASE_OPC_QUERY_FW_VER, true, 1); + ret = ubase_send_cmd(udev, &desc, 1); + if (ret) { + ubase_err(udev, "failed to query fw version, ret = %d.\n", + ret); + return ret; + } + + resp = (struct ubase_query_version_cmd *)desc.data; + *fw_version = le32_to_cpu(resp->firmware); + + ubase_info(udev, "The firmware version is %u.%u.%u.%u\n", + u32_get_bits(*fw_version, UBASE_FW_VERSION_BYTE3_MASK), + u32_get_bits(*fw_version, UBASE_FW_VERSION_BYTE2_MASK), + u32_get_bits(*fw_version, UBASE_FW_VERSION_BYTE1_MASK), + u32_get_bits(*fw_version, UBASE_FW_VERSION_BYTE0_MASK)); + + return 0; +} + +static inline void ubase_crq_table_init(struct ubase_dev *udev) +{ + struct ubase_crq_table *crq_table = &udev->crq_table; + + mutex_init(&crq_table->lock); + INIT_LIST_HEAD(&crq_table->nbs.list); +} + +static inline void ubase_crq_table_uninit(struct ubase_dev *udev) +{ + struct ubase_crq_table *crq_table = &udev->crq_table; + + mutex_destroy(&crq_table->lock); +} + +int ubase_cmd_init(struct ubase_dev *udev) +{ + int ret; + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + ubase_crq_table_init(udev); + + ret = ubase_cmd_queue_init(udev); + if (ret) { + ubase_err(udev, "failed to init ubase cmd queue.\n"); + goto err_queue_init; + } + + ubase_arq_init(udev); + + ubase_cmd_init_regs(udev); + + clear_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state); + + ret = ubase_cmd_query_version(udev, &udev->caps.dev_caps.fw_version); + if (ret) + goto err_query_version; + + return 0; + +err_query_version: + set_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state); + ubase_cmd_uninit_regs(udev); + ubase_arq_uninit(udev); + ubase_cmd_queue_uninit(udev); +err_queue_init: + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + ubase_crq_table_uninit(udev); + + return ret; +} + +void ubase_cmd_disable(struct ubase_dev *udev) +{ + __ubase_cmd_disable(udev); + /* wait to ensure the firmware completes csq commands. */ + msleep(UBASE_CMDQ_CLEAR_WAIT_TIME); + + ubase_cmd_uninit_regs(udev); +} + +void ubase_cmd_uninit(struct ubase_dev *udev) +{ + if (udev->reset_stage != UBASE_RESET_STAGE_UNINIT) { + ubase_cmd_disable(udev); + /* wait to ensure the firmware completes crq commands. */ + msleep(UBASE_CMDQ_CLEAR_WAIT_TIME); + } + + ubase_arq_uninit(udev); + ubase_cmd_queue_uninit(udev); + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + ubase_crq_table_uninit(udev); +} + +void ubase_cmd_setup_basic_desc(struct ubase_cmdq_desc *desc, + enum ubase_opcode_type opcode, bool is_read, + u8 num) +{ + memset(desc, 0, sizeof(*desc)); + desc->opcode = cpu_to_le16(opcode); + desc->flag = UBASE_CMD_FLAG_NO_INTR | UBASE_CMD_FLAG_IN; + desc->bd_num = num; + + if (is_read) + desc->flag |= cpu_to_le16(UBASE_CMD_FLAG_WR); +} + +static u16 ubase_calc_bd_num(struct ubase_cmd_buf *buf) +{ + u32 data_size = buf->data_size > UBASE_CMD_DATA_LENGTH ? + buf->data_size - UBASE_CMD_DATA_LENGTH : 0; + + return DIV_ROUND_UP(data_size, sizeof(struct ubase_cmdq_desc)) + 1; +} + +static int ubase_cmd_buf_check(struct ubase_dev *udev, struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out, u16 *num) +{ + if (in->is_read && (!out || !out->data || out->data_size == 0)) { + ubase_err(udev, "output buffer is empty.\n"); + return -EINVAL; + } + + *num = max_t(u16, ubase_calc_bd_num(in), ubase_calc_bd_num(out)); + if (*num > UBASE_CMDQ_DESC_NUM) { + ubase_err(udev, + "requested space(%u) exceeds the maximum(%lu).\n", + max_t(u32, in->data_size, out->data_size), + UBASE_CMD_MAX_DESC_SIZE); + return -EINVAL; + } + + return 0; +} + +static void ubase_cmd_setup_desc_by_inbuf(struct ubase_dev *udev, + struct ubase_cmd_buf *in, + struct ubase_cmdq_desc *desc, + u16 num) +{ + ubase_cmd_setup_basic_desc(&desc[0], in->opcode, in->is_read, num); + if (in->data) + memcpy(desc->data, in->data, in->data_size); +} + +static void ubase_cmd_setup_desc_by_outbuf(struct ubase_dev *udev, + struct ubase_cmd_buf *out, + struct ubase_cmdq_desc *desc) +{ + if (!out || out->data_size == 0) + return; + + out->opcode = desc[0].opcode; + out->is_read = (desc[0].flag & UBASE_CMD_FLAG_WR) ? true : false; + memcpy(out->data, desc->data, out->data_size); +} + +static int ubase_cmd_send_inout_real(struct ubase_dev *udev, + struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out) +{ + struct ubase_cmdq_desc *desc; + u16 num; + int ret; + + ret = ubase_cmd_buf_check(udev, in, out, &num); + if (ret) + return ret; + + desc = kcalloc(num, sizeof(struct ubase_cmdq_desc), GFP_ATOMIC); + if (!desc) { + ubase_err(udev, "failed to alloc desc, size = %lu.\n", + num * sizeof(struct ubase_cmdq_desc)); + return -ENOMEM; + } + + ubase_cmd_setup_desc_by_inbuf(udev, in, desc, num); + + ret = ubase_send_cmd(udev, desc, num); + if (ret) + goto err_send_cmd; + + ubase_cmd_setup_desc_by_outbuf(udev, out, desc); + +err_send_cmd: + kfree(desc); + + return ret; +} + +static void ubase_free_bd_data(void *msg_data, u32 bd_num) +{ + if (!msg_data || bd_num <= 1) + return; + + kfree(msg_data); +} + +static void ubase_cmd_exec_callback(struct ubase_dev *udev, u16 opcode, + void *msg_data, u32 msg_data_len) +{ + struct ubase_crq_table *crq_table = &udev->crq_table; + struct ubase_crq_event_nbs *nbs; + + if (!msg_data) + return; + + mutex_lock(&crq_table->lock); + list_for_each_entry(nbs, &crq_table->nbs.list, list) { + if (nbs->nb.opcode == opcode) { + nbs->nb.crq_handler(nbs->nb.back, msg_data, + msg_data_len); + break; + } + } + mutex_unlock(&crq_table->lock); +} + +static void ubase_gen_multi_bd_data(struct ubase_dev *udev, u32 bd_num, + void **msg_data, u32 msg_data_len) +{ + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + struct ubase_cmdq_desc *desc; + u32 pos = 0; + u32 i; + + *msg_data = kzalloc(msg_data_len, GFP_KERNEL); + if (!(*msg_data)) { + ubase_err(udev, "failed to alloc crq msg data."); + return; + } + + for (i = 0; i < bd_num; i++) { + desc = &crq->desc[crq->ci]; + trace_ubase_crq(udev->dev, i, crq->pi, crq->ci, desc); + if (i == 0) { + memcpy(*msg_data + pos, + desc->data, UBASE_CMD_DATA_LENGTH); + pos += UBASE_CMD_DATA_LENGTH; + } else { + memcpy(*msg_data + pos, desc, sizeof(*desc)); + pos += sizeof(*desc); + } + + UBASE_MOVE_CRQ_RING_PTR(crq); + } +} + +static void ubase_gen_single_bd_data(struct ubase_dev *udev, void **msg_data) +{ + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + struct ubase_cmdq_desc *desc; + + desc = &crq->desc[crq->ci]; + trace_ubase_crq(udev->dev, 1, crq->pi, crq->ci, desc); + *msg_data = crq->desc[crq->ci].data; + UBASE_MOVE_CRQ_RING_PTR(crq); +} + +static void ubase_gen_bd_data(struct ubase_dev *udev, u32 bd_num, + void **msg_data, u32 msg_data_len) +{ + if (bd_num == 1) { + ubase_gen_single_bd_data(udev, msg_data); + return; + } + + ubase_gen_multi_bd_data(udev, bd_num, msg_data, msg_data_len); +} + +static bool ubase_cmd_crq_empty(struct ubase_dev *udev, struct ubase_hw *hw) +{ + hw->cmdq.crq.pi = ubase_read_dev(hw, UBASE_CRQ_TAIL_REG); + + return hw->cmdq.crq.pi == hw->cmdq.crq.ci; +} + +void ubase_cmd_crq_handler(struct ubase_dev *udev) +{ + struct ubase_cmdq_ring *crq = &udev->hw.cmdq.crq; + u32 msg_data_len; + void *msg_data; + u16 opcode; + u8 bd_num; + u8 flag; + + while (!ubase_cmd_crq_empty(udev, &udev->hw)) { + if (test_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state)) { + ubase_warn(udev, + "command queue needs re-initializing.\n"); + return; + } + + opcode = crq->desc[crq->ci].opcode; + bd_num = crq->desc[crq->ci].bd_num; + flag = crq->desc[crq->ci].flag; + msg_data_len = bd_num * sizeof(struct ubase_cmdq_desc) - + UBASE_CMD_HEADER_LENGTH; + + if (unlikely(!bd_num || !(flag & UBASE_CMD_FLAG_OUT))) { + ubase_err(udev, + "drop invalid crq message, opcode = 0x%x, bd_num = %u, flag = 0x%x.", + opcode, bd_num, flag); + UBASE_MOVE_CRQ_RING_PTR(crq); + continue; + } + + ubase_gen_bd_data(udev, bd_num, &msg_data, msg_data_len); + + if (ubase_is_arq_msg(opcode)) + ubase_add_to_arq(udev, opcode, msg_data, msg_data_len); + else + ubase_cmd_exec_callback(udev, opcode, msg_data, + msg_data_len); + + ubase_free_bd_data(msg_data, bd_num); + } + + ubase_write_dev(&udev->hw, UBASE_CRQ_HEAD_REG, crq->ci); +} + +void ubase_crq_service_task(struct ubase_delay_work *ubase_work) +{ + struct ubase_dev *udev = container_of(ubase_work, struct ubase_dev, + service_task); + struct ubase_crq_table *crq_table = &udev->crq_table; + + if (!test_and_clear_bit(UBASE_STATE_CRQ_SERVICE_SCHED, + &udev->service_task.state) || + test_and_set_bit(UBASE_STATE_CRQ_HANDLING, + &udev->service_task.state)) + return; + + if (time_is_before_eq_jiffies(crq_table->last_crq_scheduled + + UBASE_CRQ_SCHED_TIMEOUT)) + ubase_warn(udev, + "crq service task is scheduled after %ums on cpu%d!\n", + jiffies_to_msecs(jiffies - crq_table->last_crq_scheduled), + smp_processor_id()); + + ubase_cmd_crq_handler(udev); + + clear_bit(UBASE_STATE_CRQ_HANDLING, &udev->service_task.state); +} + +static bool ubase_cmd_is_mbx_avail(struct ubase_dev *udev) +{ + return true; +} + +int ubase_cmd_mbx_event_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct ubase_event_nb *ev_nb = container_of(nb, struct ubase_event_nb, nb); + struct ubase_aeq_notify_info *info = data; + struct ubase_aeqe *aeqe = info->aeqe; + struct ubase_dev *udev = ev_nb->back; + struct ubase_mbx_event_context *ctx; + + ctx = &udev->mb_cmd.ctx; + if (aeqe->event.cmd.seq_num != ctx->seq_num) { + ubase_err(udev, + "mbx seq num is different, cmd seq_num = %u, ctx seq_num = %u.\n", + aeqe->event.cmd.seq_num, ctx->seq_num); + return NOTIFY_DONE; + } + + ctx->result = aeqe->event.cmd.status == 0 ? 0 : -EIO; + ctx->out_param = aeqe->event.cmd.out_param; + + complete(&ctx->done); + + return NOTIFY_OK; +} + +static int ubase_cmd_wait_mbx_completed(struct ubase_dev *udev, + union ubase_mbox *mbx) +{ + struct ubase_mbx_event_context *ctx = &udev->mb_cmd.ctx; + int ret; + + if (!wait_for_completion_timeout(&ctx->done, + msecs_to_jiffies(UBASE_CMDQ_MBX_TX_TIMEOUT))) { + ubase_err(udev, + "cmd seq_num 0x%x mailbox cmd code 0x%x timeout.\n", + ctx->seq_num, mbx->cmd); + return -EBUSY; + } + + ret = ctx->result; + if (ret) + ubase_err(udev, + "cmd seq_num(0x%x) mailbox cmd code(0x%x) error, ret = %d.\n", + ctx->seq_num, mbx->cmd, ret); + + return ret; +} + +static void ubase_setup_mbx_info(struct ubase_dev *udev, union ubase_mbox *mbx) +{ + mbx->seq_num = ++udev->mb_cmd.ctx.seq_num; + mbx->event_en = 1; +} + +int ubase_post_mailbox_by_event(struct ubase_dev *udev, + struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out) +{ + union ubase_mbox *mbx = (union ubase_mbox *)in->data; + unsigned long end; + int ret; + + if (!mbx) { + ubase_err(udev, "input mailbox data field is empty.\n"); + return -EINVAL; + } + + ubase_setup_mbx_info(udev, mbx); + + end = msecs_to_jiffies(UBASE_CMDQ_MBX_TX_TIMEOUT) + jiffies; + while (ubase_cmd_is_mbx_avail(udev)) { + ret = __ubase_cmd_send_inout(udev, in, out); + if (!ret) + break; + + if (time_after(jiffies, end)) { + dev_err_ratelimited(udev->dev, + "failed to wait mbox.\n"); + return -ETIMEDOUT; + } + + cond_resched(); + } + + return ubase_cmd_wait_mbx_completed(udev, mbx); +} + +int __ubase_cmd_send_inout(struct ubase_dev *udev, struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out) +{ + if (!in) { + ubase_err(udev, "input buffer is empty.\n"); + return -EINVAL; + } + + if (udev->reset_stage == UBASE_RESET_STAGE_UNINIT) + return -EAGAIN; + + return ubase_cmd_send_inout_real(udev, in, out); +} + +int __ubase_cmd_send_in(struct ubase_dev *udev, struct ubase_cmd_buf *in) +{ + struct ubase_cmd_buf out; + + out.data_size = 0; + + return __ubase_cmd_send_inout(udev, in, &out); +} + +/** + * When uninstalling, cmdq needs to be successfully sended as much as possible, + * but the cmd may be disabled during reset, this interface attempts to send cmd + * when it is enabled. + */ +int ubase_cmd_send_inout_ex(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in, struct ubase_cmd_buf *out, + u32 time_out) +{ + struct ubase_dev *udev; + u32 try_cnt = 0; + + if (!aux_dev || !in || !out) + return -EINVAL; + + udev = ubase_get_udev_by_adev(aux_dev); + if (!time_out) + return __ubase_cmd_send_inout(udev, in, out); + + while (test_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state) && + (try_cnt * UBASE_CMDQ_WAIT_TIME) < time_out) { + msleep(UBASE_CMDQ_WAIT_TIME); + try_cnt++; + } + + if ((try_cnt * UBASE_CMDQ_WAIT_TIME) >= time_out) { + ubase_warn(udev, + "cmd send timeout, due to cmd enter disable state for %ums.\n", + try_cnt * UBASE_CMDQ_WAIT_TIME); + return -EBUSY; + } + + return __ubase_cmd_send_inout(udev, in, out); +} +EXPORT_SYMBOL(ubase_cmd_send_inout_ex); + +int ubase_cmd_send_inout(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out) +{ + if (!aux_dev || !in || !out) + return -EINVAL; + + return __ubase_cmd_send_inout(__ubase_get_udev_by_adev(aux_dev), in, out); +} +EXPORT_SYMBOL(ubase_cmd_send_inout); + +int ubase_cmd_send_in_ex(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in, u32 time_out) +{ + struct ubase_cmd_buf out; + + if (!aux_dev || !in) + return -EINVAL; + + out.data_size = 0; + + return ubase_cmd_send_inout_ex(aux_dev, in, &out, time_out); +} +EXPORT_SYMBOL(ubase_cmd_send_in_ex); + +int ubase_cmd_send_in(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in) +{ + if (!aux_dev || !in) + return -EINVAL; + + return __ubase_cmd_send_in(__ubase_get_udev_by_adev(aux_dev), in); +} +EXPORT_SYMBOL(ubase_cmd_send_in); + +static int __ubase_cmd_get_data_size(struct ubase_dev *udev, u16 opcode, + u16 *data_size) +{ + struct ubase_cmdq_desc desc; + u8 bd_num; + int ret; + + ubase_cmd_setup_basic_desc(&desc, opcode, true, 1); + desc.flag |= UBASE_CMD_FLAG_GET_BD_NUM; + + ret = ubase_send_cmd(udev, &desc, 1); + if (ret) { + ubase_err(udev, + "failed to send cmd in get cmd data size, opcode = 0x%x, ret = %d.\n", + opcode, ret); + return ret; + } + + bd_num = *(u8 *)desc.data; + if (unlikely(!bd_num)) { + ubase_err(udev, + "failed to get cmd data size, bd_num = %u, opcode = 0x%x.\n", + bd_num, opcode); + return -EIO; + } + + *data_size = bd_num * sizeof(desc) - UBASE_CMD_HEADER_LENGTH; + + return 0; +} + +int ubase_cmd_get_data_size(struct auxiliary_device *aux_dev, u16 opcode, + u16 *data_size) +{ + if (!aux_dev || !data_size) + return -EINVAL; + + return __ubase_cmd_get_data_size(__ubase_get_udev_by_adev(aux_dev), + opcode, data_size); +} +EXPORT_SYMBOL(ubase_cmd_get_data_size); + +int __ubase_register_crq_event(struct ubase_dev *udev, + struct ubase_crq_event_nb *nb) +{ + struct ubase_crq_event_nbs *nbs, *tmp, *new_nbs; + struct ubase_crq_table *crq_table; + int ret; + + crq_table = &udev->crq_table; + mutex_lock(&crq_table->lock); + list_for_each_entry_safe(nbs, tmp, &crq_table->nbs.list, list) { + if (unlikely(nbs->nb.opcode == nb->opcode)) { + ret = -EEXIST; + goto err_crq_register; + } + } + + new_nbs = kzalloc(sizeof(*new_nbs), GFP_KERNEL); + if (!new_nbs) { + ret = -ENOMEM; + goto err_crq_register; + } + + new_nbs->nb = *nb; + list_add_tail(&new_nbs->list, &crq_table->nbs.list); + mutex_unlock(&crq_table->lock); + + return 0; + +err_crq_register: + mutex_unlock(&crq_table->lock); + + ubase_err(udev, "failed to register crq event, opcode = 0x%x, ret = %d.\n", + nb->opcode, ret); + return ret; +} + +int ubase_register_crq_event(struct auxiliary_device *aux_dev, + struct ubase_crq_event_nb *nb) +{ + struct ubase_dev *udev; + + if (!aux_dev || !nb || !nb->crq_handler) + return -EINVAL; + + udev = __ubase_get_udev_by_adev(aux_dev); + return __ubase_register_crq_event(udev, nb); +} +EXPORT_SYMBOL(ubase_register_crq_event); + +void __ubase_unregister_crq_event(struct ubase_dev *udev, u16 opcode) +{ + struct ubase_crq_event_nbs *nbs, *tmp; + struct ubase_crq_table *crq_table; + + crq_table = &udev->crq_table; + mutex_lock(&crq_table->lock); + list_for_each_entry_safe(nbs, tmp, &crq_table->nbs.list, list) { + if (nbs->nb.opcode == opcode) { + list_del(&nbs->list); + kfree(nbs); + break; + } + } + mutex_unlock(&crq_table->lock); +} + +void ubase_unregister_crq_event(struct auxiliary_device *aux_dev, u16 opcode) +{ + struct ubase_dev *udev; + + if (!aux_dev) + return; + + udev = __ubase_get_udev_by_adev(aux_dev); + __ubase_unregister_crq_event(udev, opcode); +} +EXPORT_SYMBOL(ubase_unregister_crq_event); + +void ubase_mask_key_words(struct ubase_cmdq_desc *desc, u16 opc, int idx) +{ + struct ubase_cfg_dma_buf_req *req; + union ubase_mbox *mb; + + switch (opc) { + case UBASE_OPC_TP_TIMER_VA_CONFIG: + case UBASE_OPC_TP_EXTDB_VA_CONFIG: + case UBASE_OPC_TA_EXTDB_VA_CONFIG: + case UBASE_OPC_TA_TIMER_VA_CONFIG: + if (idx) + return; + req = (struct ubase_cfg_dma_buf_req *)desc->data; + req->addr_l = 0; + req->addr_h = 0; + break; + case UBASE_OPC_POST_MB: + if (idx) + return; + mb = (union ubase_mbox *)desc->data; + mb->in_param_l = 0; + mb->in_param_h = 0; + break; + case UBASE_OPC_CFG_VPORT_BUF: + if (idx) + memset(desc, 0, sizeof(struct ubase_cmdq_desc)); + else + memset(desc->data, 0, sizeof(desc->data)); + break; + default: + return; + } +} diff --git a/drivers/ub/ubase/ubase_cmd.h b/drivers/ub/ubase/ubase_cmd.h new file mode 100644 index 0000000000000000000000000000000000000000..63b67179f2fb1f3ec99763e60198f41d74b9f96e --- /dev/null +++ b/drivers/ub/ubase/ubase_cmd.h @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_CMD_H__ +#define __UBASE_CMD_H__ + +#include + +#include "ubase_dev.h" + +#define UBASE_CMDQ_DESC_NUM_S 3 +#define UBASE_CMDQ_DESC_NUM 1024 +#define UBASE_CMDQ_TX_TIMEOUT 3000000 +#define UBASE_CMDQ_MBX_TX_TIMEOUT 30000 +#define UBASE_CMDQ_CLEAR_WAIT_TIME 200 +#define UBASE_CMDQ_WAIT_TIME 10 + +#define UBASE_CMD_FLAG_IN BIT(0) +#define UBASE_CMD_FLAG_OUT BIT(1) +#define UBASE_CMD_FLAG_NEXT BIT(2) +#define UBASE_CMD_FLAG_WR BIT(3) +#define UBASE_CMD_FLAG_NO_INTR BIT(4) +#define UBASE_CMD_FLAG_ERR_INTR BIT(5) +#define UBASE_CMD_FLAG_GET_BD_NUM BIT(6) + +#define UBASE_UE2UE_MSG_WAIT_TIME 3000 + +#define UBASE_CRQ_SCHED_TIMEOUT (HZ / 2) + +#define UBASE_CMD_HEADER_LENGTH 8 +#define UBASE_CMD_DATA_LENGTH (UBASE_DESC_DATA_LEN * sizeof(__le32)) +#define UBASE_CMD_MAX_DESC_SIZE \ + (UBASE_CMDQ_DESC_NUM * sizeof(struct ubase_cmdq_desc)) + +#define UBASE_MOVE_CRQ_RING_PTR(crq) \ + ((crq)->ci = ((crq)->ci + 1) % (crq)->desc_num) + +enum ubase_cmd_state { + UBASE_STATE_CMD_DISABLE +}; + +struct ubase_query_version_cmd { + __le32 firmware; + __le32 hardware; + __le32 rsv; + __le32 caps[UBASE_CAP_LEN]; +}; + +#define UBASE_UBCL_CFG_DATA_ALIGN 4 +#define UBASE_UBCL_CFG_DATA_NUM 60 +struct ubase_ubcl_config_cmd { + __le16 is_query_size; + __le16 offset; + __le16 size; + __le16 rsv; + __le32 data[UBASE_UBCL_CFG_DATA_NUM]; +}; + +enum ubase_ue2ue_sub_cmd { + UBASE_UE2UE_CTRLQ_MSG = 3, +}; + +struct ubase_ue2ue_common_head { + __le16 bus_ue_id; + __le16 mbx_ue_id; + u16 sub_cmd; + u16 status; +}; + +struct ubase_ue2ue_ctrlq_head { + struct ubase_ue2ue_common_head head; + u16 seq; + u16 in_size; + u16 out_size; + u8 need_resp : 1; + u8 is_resp : 1; + u8 rsv : 6; +}; + +struct ubase_start_perf_stats_cmd { + __le32 period; + __le32 logic_port_bitmap[2]; + u8 rsv[12]; +}; + +struct ubase_stop_perf_stats_cmd { + __le32 period; /* ms */ + __le16 port_id; + u8 rsv[2]; + + __le32 tx_port_bw; /* kbps */ + __le32 rx_port_bw; + __le32 tx_vl_bw[UBASE_STATS_MAX_VL_NUM]; + __le32 rx_vl_bw[UBASE_STATS_MAX_VL_NUM]; + u8 rsv1[8]; +}; + +struct ubase_cfg_ets_vl_sch_cmd { + __le16 vl_bitmap; + u8 rsvd[2]; + u8 vl_bw[UBASE_MAX_VL_NUM]; + __le32 port_bitmap; +}; + +struct ubase_cfg_tm_vl_sch_cmd { + __le16 bus_ue_id; + __le16 vl_bitmap; + __le16 vl_tsa; + u8 rsvd[2]; + u8 vl_bw[UBASE_MAX_VL_NUM]; +}; + +struct ubase_ets_shaping_info { + u8 ir_b; + u8 ir_u; + u8 ir_s; + u8 bs_b; + u8 bs_s; + u8 rsvd[3]; + __le32 rate; +}; + +struct ubase_query_ets_tcg_cmd { + u8 tcg_weight[UBASE_MAX_TCG_NUM]; + __le16 tcg_tc_map[UBASE_MAX_TCG_NUM]; + struct ubase_ets_shaping_info tcg_info[UBASE_MAX_TCG_NUM]; +}; + +struct ubase_query_ets_port_cmd { + struct ubase_ets_shaping_info port_info; +}; + +struct ubase_fst_revert_tbl { + __le16 fst_idx; + u8 queue_ue_num; + u8 queue_que_num; + __le16 queue_vl_num; + u8 resv[2]; +}; + +struct ubase_rqmt_tbl { + __le16 fst_idx; + __le16 start_queue_idx; + __le16 queue_quantity_shift; + u8 resv[2]; +}; + +struct ubase_query_fst_fvt_rqmt_cmd { + __le16 bus_ue_id; + u8 rsv[2]; + __le16 sl_queue_vl_num; + __le16 sl_queue_start_qid; + __le16 fvt_vl_size; + __le16 fvt_rqmt_offset; + struct ubase_fst_revert_tbl fstr_info[UBASE_MAX_VL_NUM]; + struct ubase_rqmt_tbl rqmt_info[UBASE_MAX_VL_NUM]; +}; + +struct ubase_query_tm_queue_cmd { + __le16 bus_ue_id; + u8 queue_num; + u8 resv0; + u8 queue_vl[UBASE_MAX_VL_NUM]; + u8 queue_id[UBASE_MAX_VL_NUM]; + u8 qset_id[UBASE_MAX_VL_NUM]; + __le16 link_vld_bitmap; + u8 resv1[2]; +}; + +struct ubase_query_tm_qset_cmd { + __le16 bus_ue_id; + u8 qset_num; + u8 rate_limit_bypass; + u8 ir_b[UBASE_MAX_VL_NUM]; + u8 ir_u[UBASE_MAX_VL_NUM]; + u8 ir_s[UBASE_MAX_VL_NUM]; + u8 bs_b[UBASE_MAX_VL_NUM]; + u8 bs_s[UBASE_MAX_VL_NUM]; + __le32 rate[UBASE_MAX_VL_NUM]; + u8 qset_id[UBASE_MAX_VL_NUM]; + u8 pri_id[UBASE_MAX_VL_NUM]; + __le16 qset_pri_link_vld; + __le16 qset_sch_mode; + u8 qset_weight[UBASE_MAX_VL_NUM]; + u8 resv1[16]; +}; + +struct ubase_query_tm_pri_cmd { + __le16 bus_ue_id; + u8 pri_id; + u8 pg_id; + u8 pri_sch_mode; + u8 pri_weight; + u8 c_ir_b; + u8 c_ir_u; + u8 c_ir_s; + u8 c_bs_b; + u8 c_bs_s; + u8 p_ir_b; + u8 p_ir_u; + u8 p_ir_s; + u8 p_bs_b; + u8 p_bs_s; + __le32 c_rate; + __le32 p_rate; + + u8 c_rate_limit_bypass; + u8 p_rate_limit_bypass; + u8 resv1[30]; +}; + +struct ubase_query_tm_pg_cmd { + __le16 bus_ue_id; + u8 pg_id; + u8 pg_sch_mode; + u8 pg_weight; + u8 c_ir_b; + u8 c_ir_u; + u8 c_ir_s; + u8 c_bs_b; + u8 c_bs_s; + u8 p_ir_b; + u8 p_ir_u; + u8 p_ir_s; + u8 p_bs_b; + u8 p_bs_s; + u8 resv0; + __le32 c_rate; + __le32 p_rate; + + u8 c_rate_limit_bypass; + u8 p_rate_limit_bypass; + u8 resv1[30]; +}; + +struct ubase_query_tm_port_cmd { + u8 ir_b; + u8 ir_u; + u8 ir_s; + u8 bs_b; + u8 bs_s; + u8 rate_limit_bypass; + u8 resv0[2]; + __le32 rate; + u8 resv1[12]; +}; + +struct ubase_activate_req { + __le16 bus_ue_id; + __le16 msn; + u8 activate; + u8 resv[19]; +}; + +struct ubase_activate_resp { + __le16 bus_ue_id; + __le16 msn; + u8 result; + u8 resv[19]; +}; + +struct ubase_query_ueid_cmd { + __le32 ueid[UBASE_BUS_EID_LEN]; + u32 rsv[2]; +}; + +static inline void __ubase_fill_inout_buf(struct ubase_cmd_buf *buf, u16 opcode, + bool is_read, u32 data_size, void *data) +{ + buf->opcode = opcode; + buf->is_read = is_read; + buf->data_size = data_size; + buf->data = data; +} + +int ubase_cmd_init(struct ubase_dev *udev); +void ubase_cmd_uninit(struct ubase_dev *udev); + +static inline void __ubase_cmd_enable(struct ubase_dev *udev) +{ + clear_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state); +} + +static inline void __ubase_cmd_disable(struct ubase_dev *udev) +{ + set_bit(UBASE_STATE_CMD_DISABLE, &udev->hw.state); +} + +void ubase_cmd_disable(struct ubase_dev *udev); + +void ubase_cmd_setup_basic_desc(struct ubase_cmdq_desc *desc, + enum ubase_opcode_type opcode, bool is_read, + u8 num); +int ubase_send_cmd(struct ubase_dev *udev, + struct ubase_cmdq_desc *desc, int num); + +int ubase_post_mailbox_by_event(struct ubase_dev *udev, + struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out); +int __ubase_cmd_send_in(struct ubase_dev *udev, struct ubase_cmd_buf *in); +int __ubase_cmd_send_inout(struct ubase_dev *udev, struct ubase_cmd_buf *in, + struct ubase_cmd_buf *out); + +int ubase_cmd_mbx_event_cb(struct notifier_block *nb, unsigned long action, + void *data); + +int __ubase_register_crq_event(struct ubase_dev *udev, + struct ubase_crq_event_nb *nb); +void __ubase_unregister_crq_event(struct ubase_dev *udev, u16 opcode); + +void ubase_crq_service_task(struct ubase_delay_work *ubase_work); + +void ubase_mask_key_words(struct ubase_cmdq_desc *desc, u16 opc, int idx); + +#endif diff --git a/drivers/ub/ubase/ubase_ctrlq.c b/drivers/ub/ubase/ubase_ctrlq.c new file mode 100644 index 0000000000000000000000000000000000000000..d590463b0efca062533a2f492c0aaa4682ab25b7 --- /dev/null +++ b/drivers/ub/ubase/ubase_ctrlq.c @@ -0,0 +1,1063 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "ubase_dev.h" +#include "ubase_trace.h" +#include "ubase_cmd.h" +#include "ubase_ctrlq.h" + +static inline void ubase_ctrlq_crq_table_init(struct ubase_dev *udev) +{ + struct ubase_ctrlq_crq_table *crq_tab = &udev->ctrlq.crq_table; + + mutex_init(&crq_tab->lock); + INIT_LIST_HEAD(&crq_tab->crq_nbs.list); +} + +static inline void ubase_ctrlq_crq_table_uninit(struct ubase_dev *udev) +{ + struct ubase_ctrlq_crq_table *crq_tab = &udev->ctrlq.crq_table; + + mutex_destroy(&crq_tab->lock); +} + +static inline u16 ubase_ctrlq_msg_queue_depth(struct ubase_dev *udev) +{ + return udev->ctrlq.csq.depth << 1; +} + +static inline u16 ubase_ctrlq_max_seq(struct ubase_dev *udev) +{ + return ubase_ctrlq_msg_queue_depth(udev) - 1; +} + +static int ubase_ctrlq_msg_queue_init(struct ubase_dev *udev) +{ + u16 msg_ctx_size = sizeof(struct ubase_ctrlq_msg_ctx); + u16 depth = ubase_ctrlq_msg_queue_depth(udev); + struct ubase_ctrlq_msg_ctx *ctx; + u16 i; + + udev->ctrlq.msg_queue = kzalloc(depth * msg_ctx_size, GFP_KERNEL); + if (!udev->ctrlq.msg_queue) { + ubase_err(udev, "failed to alloc ctrlq msg queue.\n"); + return -ENOMEM; + } + + for (i = 0; i < depth; i++) { + ctx = &udev->ctrlq.msg_queue[i]; + init_completion(&ctx->done); + } + + return 0; +} + +static void ubase_ctrlq_msg_queue_uninit(struct ubase_dev *udev) +{ + kfree(udev->ctrlq.msg_queue); + udev->ctrlq.msg_queue = NULL; +} + +static int ubase_ctrlq_map_queue(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + u32 addr_h, addr_l; + u64 queue_addr; + size_t size; + + if (!ubase_dev_ctrlq_supported(udev)) + return 0; + + addr_h = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CSQ_BASEADDR_H_REG); + addr_l = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CSQ_BASEADDR_L_REG); + queue_addr = ubase_addr_gen(addr_h, addr_l); + size = csq->depth * UBASE_CTRLQ_BB_LEN; + csq->base_addr = devm_ioremap(udev->dev, queue_addr, size); + if (!csq->base_addr) { + ubase_err(udev, "failed to map ctrlq csq base addr, size = %lu.\n", + size); + return -ENOMEM; + } + + addr_h = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CRQ_BASEADDR_H_REG); + addr_l = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CRQ_BASEADDR_L_REG); + queue_addr = ubase_addr_gen(addr_h, addr_l); + size = crq->depth * UBASE_CTRLQ_BB_LEN; + crq->base_addr = devm_ioremap(udev->dev, queue_addr, size); + if (!crq->base_addr) { + ubase_err(udev, "failed to map ctrlq crq base addr, size = %lu.\n", + size); + goto err_map_crq; + } + + return 0; + +err_map_crq: + devm_iounmap(udev->dev, csq->base_addr); + csq->base_addr = NULL; + return -ENOMEM; +} + +static void ubase_ctrlq_unmap_queue(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + + if (!ubase_dev_ctrlq_supported(udev)) + return; + + if (csq->base_addr) { + devm_iounmap(udev->dev, csq->base_addr); + csq->base_addr = NULL; + } + + if (crq->base_addr) { + devm_iounmap(udev->dev, crq->base_addr); + crq->base_addr = NULL; + } +} + +static void ubase_ctrlq_queue_pi_ci_init(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + u16 crq_hw_pi; + + if (!ubase_dev_ctrlq_supported(udev)) + return; + + csq->pi = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CSQ_TAIL_REG); + csq->ci = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CSQ_HEAD_REG); + + crq_hw_pi = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CRQ_TAIL_REG); + ubase_write_dev(&udev->hw, UBASE_CTRLQ_CRQ_HEAD_REG, crq_hw_pi); + crq->pi = crq_hw_pi; + crq->ci = crq_hw_pi; +} + +static int ubase_query_ctrlq_queue_info(struct ubase_dev *udev) +{ +#define UBASE_CTRLQ_QUEUE_DEFAULT 2048 + + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + + csq->depth = UBASE_CTRLQ_QUEUE_DEFAULT; + crq->depth = UBASE_CTRLQ_QUEUE_DEFAULT; + + return 0; +} + +static int ubase_ctrlq_get_queue_depth(struct ubase_dev *udev) +{ +#define ubase_reg_val_to_depth(a) ((a) << 3) + + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + u16 reg_val; + + if (!ubase_dev_ctrlq_supported(udev)) + return ubase_query_ctrlq_queue_info(udev); + + reg_val = (u16)ubase_read_dev(&udev->hw, UBASE_CTRLQ_CSQ_DEPTH_REG); + csq->depth = ubase_reg_val_to_depth(reg_val); + if (!csq->depth) { + ubase_err(udev, "the csq depth is 0.\n"); + return -EINVAL; + } + + reg_val = (u16)ubase_read_dev(&udev->hw, UBASE_CTRLQ_CRQ_DEPTH_REG); + crq->depth = ubase_reg_val_to_depth(reg_val); + if (!crq->depth) { + ubase_err(udev, "the crq depth is 0.\n"); + return -EINVAL; + } + + return 0; +} + +static int ubase_ctrlq_queue_init(struct ubase_dev *udev) +{ +#define CTRLQ_TX_TIMEOUT 3000 + + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + int ret; + + spin_lock_init(&csq->lock); + spin_lock_init(&crq->lock); + + ret = ubase_ctrlq_get_queue_depth(udev); + if (ret) { + ubase_err(udev, "failed to get queue depth, ret = %d.\n", + ret); + return ret; + } + + csq->tx_timeout = CTRLQ_TX_TIMEOUT; + ubase_ctrlq_queue_pi_ci_init(udev); + + ret = ubase_ctrlq_map_queue(udev); + if (ret) + ubase_err(udev, "failed to map ctrlq queue, ret = %d.\n", + ret); + + return ret; +} + +static void ubase_ctrlq_queue_uninit(struct ubase_dev *udev) +{ + ubase_ctrlq_unmap_queue(udev); +} + +int ubase_ctrlq_init(struct ubase_dev *udev) +{ + int ret; + + if (test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + ubase_ctrlq_queue_pi_ci_init(udev); + goto success; + } + + ret = ubase_ctrlq_queue_init(udev); + if (ret) + return ret; + + ret = ubase_ctrlq_msg_queue_init(udev); + if (ret) + goto err_msg_queue_init; + + udev->ctrlq.csq_next_seq = 1; + atomic_set(&udev->ctrlq.req_cnt, 0); + ubase_ctrlq_crq_table_init(udev); + +success: + set_bit(UBASE_CTRLQ_STATE_ENABLE, &udev->ctrlq.state); + return 0; + +err_msg_queue_init: + ubase_ctrlq_queue_uninit(udev); + return ret; +} + +static void ubase_ctrlq_clean_msg_queue(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + u16 depth = ubase_ctrlq_msg_queue_depth(udev); + struct ubase_ctrlq_msg_ctx *ctx; + u16 i; + + spin_lock_bh(&csq->lock); + for (i = 0; i < depth; i++) { + ctx = &udev->ctrlq.msg_queue[i]; + ctx->valid = 0; + } + spin_unlock_bh(&csq->lock); +} + +void ubase_ctrlq_disable_remote(struct ubase_dev *udev) +{ + struct ubase_ctrlq_chan_ctrl_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + u32 resp; + int ret; + + if (!ubase_dev_ctrlq_supported(udev)) + return; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_DEV_REGISTER; + msg.opcode = UBASE_CTRLQ_OPC_CTRLQ_CTRL; + msg.need_resp = 1; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + req.opc = UBASE_CTRLQ_CHAN_DISABLE_OPC; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) + ubase_err(udev, "failed to disable remote ctrlq, ret = %d.\n", + ret); +} + +static void ubase_ctrlq_clean_pending_msgs(struct ubase_dev *udev) +{ +#define UBASE_CTRLQ_CLEAN_WAIT_TIME 5 + + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + u16 depth = ubase_ctrlq_msg_queue_depth(udev); + struct ubase_ctrlq_msg_ctx *ctx; + u16 i; + + spin_lock_bh(&csq->lock); + for (i = 1; i < depth; i++) { + ctx = &udev->ctrlq.msg_queue[i]; + if (!completion_done(&ctx->done)) + complete(&ctx->done); + } + spin_unlock_bh(&csq->lock); + + while (atomic_read(&udev->ctrlq.req_cnt)) + msleep(UBASE_CTRLQ_CLEAN_WAIT_TIME); +} + +void ubase_ctrlq_disable(struct ubase_dev *udev) +{ +#define UBASE_CTRLQ_CLEAR_WAIT_TIME 5 + + clear_bit(UBASE_CTRLQ_STATE_ENABLE, &udev->ctrlq.state); + + /* wait to ensure that the crq completes the possible left + * over commands. + */ + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + while (test_bit(UBASE_STATE_CTRLQ_HANDLING, + &udev->service_task.state)) + msleep(UBASE_CTRLQ_CLEAR_WAIT_TIME); + } + + ubase_ctrlq_clean_pending_msgs(udev); +} + +void ubase_ctrlq_uninit(struct ubase_dev *udev) +{ + if (udev->reset_stage != UBASE_RESET_STAGE_UNINIT) + ubase_ctrlq_disable(udev); + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + ubase_ctrlq_crq_table_uninit(udev); + ubase_ctrlq_msg_queue_uninit(udev); + ubase_ctrlq_queue_uninit(udev); + } else { + ubase_ctrlq_clean_msg_queue(udev); + } +} + +static u16 ubase_ctrlq_calc_bb_num(u16 in_size) +{ + u16 data_size = in_size > UBASE_CTRLQ_DATA_LEN ? + in_size - UBASE_CTRLQ_DATA_LEN : 0; + + return (DIV_ROUND_UP(data_size, UBASE_CTRLQ_BB_LEN) + 1); +} + +static u16 ubase_ctrlq_remain_space(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + u16 used; + + used = (csq->pi - csq->ci + csq->depth) % csq->depth; + + return csq->depth - used - 1; +} + +static void ubase_ctrlq_fill_first_bb(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + struct ubase_ctrlq_msg *msg, + struct ubase_ctrlq_ue_info *ue_info) +{ + struct ub_entity *ue = to_ub_entity(udev->dev); + + head->service_ver = msg->service_ver; + head->service_type = msg->service_type; + head->opcode = msg->opcode; + head->mbx_ue_id = ue_info ? ue_info->mbx_ue_id : 0; + head->ret = msg->is_resp ? msg->resp_ret : 0; + head->bus_ue_id = cpu_to_le16(ue_info ? + ue_info->bus_ue_id : + ue->entity_idx); + memcpy(head->data, msg->in, min(msg->in_size, UBASE_CTRLQ_DATA_LEN)); +} + +static inline void ubase_ctrlq_csq_report_irq(struct ubase_dev *udev) +{ +#define UBASE_CTRLQ_CSQ_IRQ_EN BIT(16) +#define UBASE_CTRLQ_CSQ_IRQ_VEC_IDX 0 + + u32 val = UBASE_CTRLQ_CSQ_IRQ_EN | UBASE_CTRLQ_CSQ_IRQ_VEC_IDX; + + ubase_write_reg(udev->hw.rs0_base.addr, 0, val); +} + +static int ubase_ctrlq_send_to_cmdq(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + struct ubase_ctrlq_msg *msg, u8 num) +{ + u32 req_len = msg->in_size + sizeof(struct ubase_ue2ue_ctrlq_head) + + UBASE_CTRLQ_HDR_LEN; + struct ubase_ue2ue_ctrlq_head ue2ue_head = {0}; + u16 seq = le16_to_cpu(head->seq); + struct ubase_cmd_buf in; + void *req; + int ret; + + req = kzalloc(req_len, GFP_ATOMIC); + if (!req) + return -ENOMEM; + + ue2ue_head.head.sub_cmd = UBASE_UE2UE_CTRLQ_MSG; + ue2ue_head.seq = seq; + ue2ue_head.in_size = msg->in_size; + ue2ue_head.out_size = msg->out_size; + ue2ue_head.need_resp = msg->need_resp; + ue2ue_head.is_resp = msg->is_resp; + memcpy(req, &ue2ue_head, sizeof(ue2ue_head)); + memcpy((u8 *)req + sizeof(ue2ue_head), head, UBASE_CTRLQ_HDR_LEN); + memcpy((u8 *)req + sizeof(ue2ue_head) + UBASE_CTRLQ_HDR_LEN, msg->in, + msg->in_size); + + __ubase_fill_inout_buf(&in, UBASE_OPC_UE2UE_UBASE, false, req_len, req); + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, + "failed to send ue2ue ctrlq msg, seq = %u, ret = %d.\n", + seq, ret); + + kfree(req); + return ret; +} + +static void ubase_ctrlq_send_to_csq(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + struct ubase_ctrlq_msg *msg, u8 num) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + u32 total_size = msg->in_size; + u32 size, offset = 0; + u8 cnt = 0; + u8 *addr; + + while (cnt < num) { + addr = csq->base_addr + csq->pi * UBASE_CTRLQ_BB_LEN; + if (cnt == 0) { + memcpy_toio(addr, head, sizeof(*head)); + trace_ubase_ctrlq_csq(udev->dev, num, csq->pi, csq->ci, + head, sizeof(*head)); + total_size -= UBASE_CTRLQ_DATA_LEN; + offset += UBASE_CTRLQ_DATA_LEN; + } else { + size = min_t(u32, total_size, UBASE_CTRLQ_BB_LEN); + memcpy_toio(addr, (u8 *)msg->in + offset, size); + trace_ubase_ctrlq_csq(udev->dev, num, csq->pi, csq->ci, + (u8 *)msg->in + offset, size); + total_size -= size; + offset += size; + } + csq->pi++; + if (csq->pi >= csq->depth) + csq->pi = 0; + + cnt++; + } + + ubase_write_dev(&udev->hw, UBASE_CTRLQ_CSQ_TAIL_REG, csq->pi); + ubase_ctrlq_csq_report_irq(udev); +} + +static int ubase_ctrlq_send_msg_to_sq(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + struct ubase_ctrlq_msg *msg, u8 num) +{ + if (ubase_dev_ctrlq_supported(udev)) { + ubase_ctrlq_send_to_csq(udev, head, msg, num); + return 0; + } + + return ubase_ctrlq_send_to_cmdq(udev, head, msg, num); +} + +static int ubase_ctrlq_wait_completed(struct ubase_dev *udev, u16 seq, + struct ubase_ctrlq_msg *msg) +{ + struct ubase_ctrlq_msg_ctx *ctx = &udev->ctrlq.msg_queue[seq]; + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + int ret; + + if (!wait_for_completion_timeout(&ctx->done, + msecs_to_jiffies(csq->tx_timeout))) { + ubase_err(udev, + "ctrlq wait resp timeout, seq = %u, opcode = 0x%x, service_type = 0x%x.\n", + seq, msg->opcode, msg->service_type); + return -ETIMEDOUT; + } + + ret = ctx->result; + if (ret) + ubase_err(udev, + "ctrlq recv failed resp for seq = %u, opcode = 0x%x, service_type = 0x%x, ret = %d.\n", + seq, msg->opcode, msg->service_type, ret); + + return -ret; +} + +static int ubase_ctrlq_alloc_seq(struct ubase_dev *udev, + struct ubase_ctrlq_msg *msg, u16 *seq) +{ + struct ubase_ctrlq_msg_ctx *ctx = udev->ctrlq.msg_queue; + u16 max_seq = ubase_ctrlq_max_seq(udev); + u16 next_seq = udev->ctrlq.csq_next_seq; + u32 i; + + for (i = next_seq; i <= max_seq; i++) { + if (!ctx[i].valid) + goto success; + } + + /* seq 0 is not used. */ + for (i = 1; i < next_seq; i++) { + if (!ctx[i].valid) + goto success; + } + + return -EBUSY; + +success: + *seq = i; + udev->ctrlq.csq_next_seq = i + 1; + + return 0; +} + +static void ubase_ctrlq_free_seq(struct ubase_dev *udev, u16 seq) +{ + struct ubase_ctrlq_msg_ctx *ctx = &udev->ctrlq.msg_queue[seq]; + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + + spin_lock_bh(&csq->lock); + ctx->valid = 0; + spin_unlock_bh(&csq->lock); +} + +static void ubase_ctrlq_addto_msg_queue(struct ubase_dev *udev, u16 seq, + struct ubase_ctrlq_msg *msg, + struct ubase_ctrlq_ue_info *ue_info) +{ + struct ubase_ctrlq_msg_ctx *ctx; + + if (!msg->need_resp) + return; + + ctx = &udev->ctrlq.msg_queue[seq]; + ctx->valid = 1; + ctx->is_sync = msg->need_resp && msg->out && msg->out_size ? 1 : 0; + ctx->result = ETIME; + ctx->dead_jiffies = jiffies + msecs_to_jiffies(UBASE_CTRLQ_DEAD_TIME); + if (ctx->is_sync) { + ctx->out = msg->out; + ctx->out_size = msg->out_size; + } + + if (ue_info) { + ctx->ue_seq = ue_info->seq; + ctx->bus_ue_id = ue_info->bus_ue_id; + } + reinit_completion(&ctx->done); +} + +static int ubase_ctrlq_msg_check(struct ubase_dev *udev, + struct ubase_ctrlq_msg *msg) +{ + if (!msg || !msg->in || !msg->in_size) { + ubase_err(udev, "ctrlq input buf is invalid.\n"); + return -EINVAL; + } + + if (msg->is_resp && !(msg->resp_seq & UBASE_CTRLQ_SEQ_MASK)) { + ubase_err(udev, "ctrlq input resp_seq(%u) is invalid.\n", + msg->resp_seq); + return -EINVAL; + } + + if (msg->in_size > UBASE_CTRLQ_MAX_DATA_SIZE) { + ubase_err(udev, + "requested ctrlq space(%u) exceeds the maximum(%u).\n", + msg->in_size, UBASE_CTRLQ_MAX_DATA_SIZE); + return -EINVAL; + } + + return 0; +} + +static int ubase_ctrlq_check_csq_enough(struct ubase_dev *udev, u16 num) +{ + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + + if (!ubase_dev_ctrlq_supported(udev)) + return 0; + + csq->ci = (u16)ubase_read_dev(&udev->hw, UBASE_CTRLQ_CSQ_HEAD_REG); + if (num > ubase_ctrlq_remain_space(udev)) { + ubase_warn(udev, + "no enough space in ctrlq, ci = %u, num = %u.\n", + csq->ci, num); + return -EBUSY; + } + + return 0; +} + +static int ubase_ctrlq_send_real(struct ubase_dev *udev, + struct ubase_ctrlq_msg *msg, + struct ubase_ctrlq_ue_info *ue_info) +{ + bool sync_req = msg->out && msg->out_size && msg->need_resp; + bool no_resp = !msg->is_resp && !msg->need_resp; + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_base_block head = {0}; + u16 seq, num; + int ret; + + num = ubase_ctrlq_calc_bb_num(msg->in_size); + + spin_lock_bh(&csq->lock); + + ret = ubase_ctrlq_check_csq_enough(udev, num); + if (ret) + goto unlock; + + if (!msg->is_resp) { + ret = ubase_ctrlq_alloc_seq(udev, msg, &seq); + if (ret) { + ubase_warn(udev, "no enough seq in ctrlq.\n"); + goto unlock; + } + } else { + seq = msg->resp_seq; + } + + ubase_ctrlq_addto_msg_queue(udev, seq, msg, ue_info); + + head.bb_num = num; + head.seq = cpu_to_le16(seq); + ubase_ctrlq_fill_first_bb(udev, &head, msg, ue_info); + ret = ubase_ctrlq_send_msg_to_sq(udev, &head, msg, num); + if (ret) { + spin_unlock_bh(&csq->lock); + goto free_seq; + } + + spin_unlock_bh(&csq->lock); + + if (sync_req) + ret = ubase_ctrlq_wait_completed(udev, seq, msg); + +free_seq: + /* Only the seqs in synchronous requests and no response requests need to be released. */ + /* The seqs are released in periodic tasks of asynchronous requests. */ + if (sync_req || no_resp) + ubase_ctrlq_free_seq(udev, seq); + return ret; +unlock: + spin_unlock_bh(&csq->lock); + return ret; +} + +int __ubase_ctrlq_send(struct ubase_dev *udev, struct ubase_ctrlq_msg *msg, + struct ubase_ctrlq_ue_info *ue_info) +{ +#define UBASE_CTRLQ_RETRY_TIMES 3 +#define UBASE_RETRY_INTERVAL 100 + + int ret, retry_cnt = 0; + + ret = ubase_ctrlq_msg_check(udev, msg); + if (ret) + return ret; + + while (retry_cnt++ <= UBASE_CTRLQ_RETRY_TIMES) { + if (udev->reset_stage == UBASE_RESET_STAGE_UNINIT && + !(msg->opcode == UBASE_CTRLQ_OPC_CTRLQ_CTRL && + msg->service_type == UBASE_CTRLQ_SER_TYPE_DEV_REGISTER)) { + ubase_dbg(udev, "ctrlq send is disabled.\n"); + return -EAGAIN; + } + + if (!test_bit(UBASE_CTRLQ_STATE_ENABLE, &udev->ctrlq.state)) { + ubase_warn(udev, "ctrlq is disabled in csq.\n"); + return -EAGAIN; + } + + atomic_inc(&udev->ctrlq.req_cnt); + ret = ubase_ctrlq_send_real(udev, msg, ue_info); + atomic_dec(&udev->ctrlq.req_cnt); + if (ret == -ETIMEDOUT && retry_cnt <= UBASE_CTRLQ_RETRY_TIMES) { + ubase_info(udev, + "Ctrlq send msg retry, retry cnt = %d.\n", + retry_cnt); + msleep(UBASE_RETRY_INTERVAL); + } else { + break; + } + } + + return ret; +} + +int ubase_ctrlq_send_msg(struct auxiliary_device *aux_dev, + struct ubase_ctrlq_msg *msg) +{ + if (!aux_dev || !msg) + return -EINVAL; + + return __ubase_ctrlq_send(__ubase_get_udev_by_adev(aux_dev), msg, NULL); +} +EXPORT_SYMBOL(ubase_ctrlq_send_msg); + +static bool ubase_ctrlq_crq_is_empty(struct ubase_dev *udev, struct ubase_hw *hw) +{ + udev->ctrlq.crq.pi = ubase_read_dev(hw, UBASE_CTRLQ_CRQ_TAIL_REG); + + return udev->ctrlq.crq.pi == udev->ctrlq.crq.ci; +} + +static void ubase_ctrlq_update_crq_ci(struct ubase_dev *udev, u8 bb_num) +{ + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + + crq->ci = (crq->ci + bb_num) % crq->depth; + ubase_write_dev(&udev->hw, UBASE_CTRLQ_CRQ_HEAD_REG, crq->ci); +} + +static void ubase_ctrlq_read_msg_data(struct ubase_dev *udev, u8 num, u8 *msg) +{ + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + u16 pos = crq->ci; + u8 i; + + for (i = 0; i < num; i++) { + memcpy_fromio(msg + i * UBASE_CTRLQ_BB_LEN, + (u8 *)crq->base_addr + pos * UBASE_CTRLQ_BB_LEN, + UBASE_CTRLQ_BB_LEN); + trace_ubase_ctrlq_crq(udev->dev, num, crq->pi, crq->ci, + msg + i * UBASE_CTRLQ_BB_LEN, + UBASE_CTRLQ_BB_LEN); + pos = (pos + 1) % crq->depth; + } +} + +static void ubase_ctrlq_crq_event_callback(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + void *msg_data, u16 msg_data_len, + u16 seq) +{ + struct ubase_ctrlq_crq_table *crq_tab = &udev->ctrlq.crq_table; + struct ubase_ctrlq_crq_event_nbs *nbs; + + mutex_lock(&crq_tab->lock); + list_for_each_entry(nbs, &crq_tab->crq_nbs.list, list) { + if (nbs->crq_nb.service_type == head->service_type && + nbs->crq_nb.opcode == head->opcode) { + nbs->crq_nb.crq_handler(nbs->crq_nb.back, + head->service_ver, + msg_data, + msg_data_len, + seq); + break; + } + } + mutex_unlock(&crq_tab->lock); +} + +static void ubase_ctrlq_notify_completed(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + u16 seq, void *msg, u16 msg_len) +{ + struct ubase_ctrlq_msg_ctx *ctx; + + ctx = &udev->ctrlq.msg_queue[seq]; + ctx->result = head->ret; + memcpy(ctx->out, msg, min(msg_len, ctx->out_size)); + + complete(&ctx->done); +} + +static bool ubase_ctrlq_check_seq(struct ubase_dev *udev, u16 seq) +{ + bool is_pushed = !!(seq & UBASE_CTRLQ_SEQ_MASK); + u16 max_seq = ubase_ctrlq_max_seq(udev); + + return is_pushed || (seq && seq <= max_seq); +} + +void ubase_ctrlq_handle_crq_msg(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + u16 seq, void *msg_data, u16 data_len) +{ + bool is_pushed = !!(seq & UBASE_CTRLQ_SEQ_MASK); + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ctrlq_msg_ctx *ctx; + + if (!is_pushed) { + spin_lock_bh(&csq->lock); + ctx = &udev->ctrlq.msg_queue[seq]; + if (!ctx->valid) { + ubase_warn(udev, + "seq is invalid, opcode = 0x%x, service_type = 0x%x, seq = %u.\n", + head->opcode, head->service_type, seq); + goto unlock; + } + if (ctx->is_sync) { + ubase_ctrlq_notify_completed(udev, head, seq, msg_data, + data_len); + goto unlock; + } + ctx->valid = 0; + spin_unlock_bh(&csq->lock); + } + + ubase_ctrlq_crq_event_callback(udev, head, msg_data, data_len, seq); + return; + +unlock: + spin_unlock_bh(&csq->lock); +} + +static void ubase_ctrlq_handle_self_msg(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head) +{ + u16 seq = le16_to_cpu(head->seq); + u16 msg_len, data_len; + void *msg; + + msg_len = head->bb_num * UBASE_CTRLQ_BB_LEN; + msg = kzalloc(msg_len, GFP_KERNEL); + if (!msg) { + ubase_err(udev, + "failed to alloc ctrlq crq msg data, opcode = 0x%x, service_type = 0x%x, seq = %u.\n", + head->opcode, head->service_type, seq); + return; + } + + ubase_ctrlq_read_msg_data(udev, head->bb_num, msg); + data_len = msg_len - UBASE_CTRLQ_HDR_LEN; + + ubase_ctrlq_handle_crq_msg(udev, head, seq, + (u8 *)msg + UBASE_CTRLQ_HDR_LEN, data_len); + + kfree(msg); +} + +static void ubase_ctrlq_handle_other_msg(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head) +{ + u16 resp_len, seq = le16_to_cpu(head->seq); + bool is_pushed = !!(seq & UBASE_CTRLQ_SEQ_MASK); + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + struct ubase_ue2ue_ctrlq_head *ue2ue_head; + struct ubase_ctrlq_msg_ctx ctx = {0}; + struct ubase_cmd_buf in; + void *resp, *msg; + int ret; + + if (!is_pushed) { + spin_lock_bh(&csq->lock); + ctx = udev->ctrlq.msg_queue[seq]; + if (!ctx.valid) { + ubase_warn(udev, "invalid seq = %u, opcode = 0x%x, service_type = 0x%x.\n", + seq, head->opcode, head->service_type); + spin_unlock_bh(&csq->lock); + return; + } + if (!ctx.is_sync) + udev->ctrlq.msg_queue[seq].valid = 0; + spin_unlock_bh(&csq->lock); + } + + resp_len = head->bb_num * UBASE_CTRLQ_BB_LEN + + sizeof(struct ubase_ue2ue_ctrlq_head); + resp = kzalloc(resp_len, GFP_KERNEL); + if (!resp) { + ubase_err(udev, "failed to alloc resp mem, seq = %u.\n", seq); + return; + } + + msg = (u8 *)resp + sizeof(struct ubase_ue2ue_ctrlq_head); + ubase_ctrlq_read_msg_data(udev, head->bb_num, msg); + + ue2ue_head = (struct ubase_ue2ue_ctrlq_head *)resp; + ue2ue_head->head.sub_cmd = UBASE_UE2UE_CTRLQ_MSG; + ue2ue_head->head.bus_ue_id = is_pushed ? head->bus_ue_id : + cpu_to_le16(ctx.bus_ue_id); + ue2ue_head->seq = is_pushed ? seq : ctx.ue_seq; + __ubase_fill_inout_buf(&in, UBASE_OPC_UE2UE_UBASE, false, resp_len, resp); + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_warn(udev, + "failed to send ue ctrlq msg, opc = 0x%x, service_type = 0x%x, ret = %d.\n", + head->opcode, head->service_type, ret); + + kfree(resp); +} + +static inline void ubase_ctrlq_reset_crq_ci(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + + crq->pi = ubase_read_dev(&udev->hw, UBASE_CTRLQ_CRQ_TAIL_REG); + crq->ci = crq->pi; + ubase_write_dev(&udev->hw, UBASE_CTRLQ_CRQ_HEAD_REG, crq->ci); +} + +static void ubase_ctrlq_crq_handler(struct ubase_dev *udev) +{ + struct ubase_ctrlq_ring *crq = &udev->ctrlq.crq; + struct ub_entity *ue = to_ub_entity(udev->dev); + struct ubase_ctrlq_base_block head = {0}; + u8 bb_num; + u8 *addr; + u16 seq; + + while (!ubase_ctrlq_crq_is_empty(udev, &udev->hw)) { + if (!test_bit(UBASE_CTRLQ_STATE_ENABLE, &udev->ctrlq.state)) { + ubase_warn(udev, "ctrlq is disabled in crq.\n"); + return; + } + + addr = crq->base_addr + crq->ci * UBASE_CTRLQ_BB_LEN; + memcpy_fromio(&head, addr, UBASE_CTRLQ_HDR_LEN); + seq = le16_to_cpu(head.seq); + bb_num = head.bb_num; + + if (unlikely(!bb_num || bb_num > UBASE_CTRLQ_MAX_BB)) { + ubase_err(udev, "ctrlq crq bb_num(%u) is invalid.\n", + bb_num); + ubase_ctrlq_reset_crq_ci(udev); + return; + } + + if (!ubase_ctrlq_check_seq(udev, seq)) { + ubase_warn(udev, + "ctrlq recv invalid seq, seq = %u.\n", seq); + ubase_ctrlq_update_crq_ci(udev, bb_num); + continue; + } + + if (le16_to_cpu(head.bus_ue_id) == ue->entity_idx) + ubase_ctrlq_handle_self_msg(udev, &head); + else + ubase_ctrlq_handle_other_msg(udev, &head); + + ubase_ctrlq_update_crq_ci(udev, bb_num); + } +} + +void ubase_ctrlq_service_task(struct ubase_delay_work *ubase_work) +{ + struct ubase_dev *udev = container_of(ubase_work, struct ubase_dev, + service_task); + struct ubase_ctrlq_crq_table *crq_tab = &udev->ctrlq.crq_table; + + if (!test_and_clear_bit(UBASE_STATE_CTRLQ_SERVICE_SCHED, + &udev->service_task.state) || + test_and_set_bit(UBASE_STATE_CTRLQ_HANDLING, + &udev->service_task.state)) + return; + + if (time_is_before_eq_jiffies(crq_tab->last_crq_scheduled + + UBASE_CTRLQ_SCHED_TIMEOUT)) + ubase_warn(udev, + "ctrlq crq service task is scheduled after %ums on cpu%d!\n", + jiffies_to_msecs(jiffies - crq_tab->last_crq_scheduled), + smp_processor_id()); + + ubase_ctrlq_crq_handler(udev); + + clear_bit(UBASE_STATE_CTRLQ_HANDLING, &udev->service_task.state); +} + +void ubase_ctrlq_clean_service_task(struct ubase_delay_work *ubase_work) +{ + struct ubase_dev *udev = container_of(ubase_work, struct ubase_dev, + service_task); + struct ubase_ctrlq_ring *csq = &udev->ctrlq.csq; + u16 i, max_seq = ubase_ctrlq_max_seq(udev); + struct ubase_ctrlq_msg_ctx *ctx; + + if (!test_bit(UBASE_CTRLQ_STATE_ENABLE, &udev->ctrlq.state) || + ubase_dev_pmu_supported(udev)) + return; + + spin_lock_bh(&csq->lock); + for (i = 1; i <= max_seq; i++) { + ctx = &udev->ctrlq.msg_queue[i]; + if (ctx->valid && time_is_before_eq_jiffies(ctx->dead_jiffies)) + ctx->valid = 0; + } + spin_unlock_bh(&csq->lock); +} + +int ubase_ctrlq_register_crq_event(struct auxiliary_device *aux_dev, + struct ubase_ctrlq_event_nb *nb) +{ + struct ubase_ctrlq_crq_event_nbs *nbs, *tmp, *new_nbs; + struct ubase_ctrlq_crq_table *crq_tab; + struct ubase_dev *udev; + int ret; + + if (!aux_dev || !nb || !nb->crq_handler) + return -EINVAL; + + udev = __ubase_get_udev_by_adev(aux_dev); + crq_tab = &udev->ctrlq.crq_table; + mutex_lock(&crq_tab->lock); + list_for_each_entry_safe(nbs, tmp, &crq_tab->crq_nbs.list, list) { + if (nbs->crq_nb.service_type == nb->service_type && + nbs->crq_nb.opcode == nb->opcode) { + ret = -EEXIST; + goto err_crq_register; + } + } + + new_nbs = kzalloc(sizeof(*new_nbs), GFP_KERNEL); + if (!new_nbs) { + ret = -ENOMEM; + goto err_crq_register; + } + + new_nbs->crq_nb = *nb; + list_add_tail(&new_nbs->list, &crq_tab->crq_nbs.list); + mutex_unlock(&crq_tab->lock); + + return 0; + +err_crq_register: + mutex_unlock(&crq_tab->lock); + ubase_err(udev, + "failed to register ctrlq crq event, opcode = 0x%x, service_type = 0x%x, ret = %d.\n", + nb->opcode, nb->service_type, ret); + + return ret; +} +EXPORT_SYMBOL(ubase_ctrlq_register_crq_event); + +void ubase_ctrlq_unregister_crq_event(struct auxiliary_device *aux_dev, + u8 service_type, u8 opcode) +{ + struct ubase_ctrlq_crq_event_nbs *nbs, *tmp; + struct ubase_ctrlq_crq_table *crq_tab; + struct ubase_dev *udev; + + if (!aux_dev) + return; + + udev = __ubase_get_udev_by_adev(aux_dev); + crq_tab = &udev->ctrlq.crq_table; + mutex_lock(&crq_tab->lock); + list_for_each_entry_safe(nbs, tmp, &crq_tab->crq_nbs.list, list) { + if (nbs->crq_nb.service_type == service_type && + nbs->crq_nb.opcode == opcode) { + list_del(&nbs->list); + kfree(nbs); + break; + } + } + mutex_unlock(&crq_tab->lock); +} +EXPORT_SYMBOL(ubase_ctrlq_unregister_crq_event); diff --git a/drivers/ub/ubase/ubase_ctrlq.h b/drivers/ub/ubase/ubase_ctrlq.h new file mode 100644 index 0000000000000000000000000000000000000000..431253df50540a95a1eba740c6e70db816e5fbfa --- /dev/null +++ b/drivers/ub/ubase/ubase_ctrlq.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_CTRLQ_H__ +#define __UBASE_CTRLQ_H__ + +#include + +#include "ubase_dev.h" + +#define UBASE_CTRLQ_TX_TIMEOUT 30000 +#define UBASE_CTRLQ_BB_LEN 32U +#define UBASE_CTRLQ_HDR_LEN 12 +#define UBASE_CTRLQ_DATA_LEN (UBASE_CTRLQ_BB_LEN - UBASE_CTRLQ_HDR_LEN) +#define UBASE_CTRLQ_MAX_BB 32 +#define UBASE_CTRLQ_SCHED_TIMEOUT (HZ / 2) +#define UBASE_CTRLQ_SEQ_MASK BIT(15) +#define UBASE_CTRLQ_DEAD_TIME 40000 +#define UBASE_CTRLQ_CHAN_DISABLE_OPC 0x1 +#define UBASE_CTRL_PLANE_INIT_RES BIT(0) + +enum ubase_ctrlq_state { + UBASE_CTRLQ_STATE_ENABLE, +}; + +struct ubase_ctrlq_ue_info { + u8 mbx_ue_id; + u16 seq; + u16 bus_ue_id; +}; + +struct ubase_ctrlq_create_tp_req { + u8 trans_type : 4; + u8 rsv0 : 4; + u8 vl; + u8 rsv1[2]; +}; + +struct ubase_ctrlq_create_tp_resp { + __le32 tpgn : 24; + __le32 rsv : 8; + __le32 tpn_cnt : 8; + __le32 start_tpn : 24; +}; + +struct ubase_ctrlq_destroy_tp_req { + u8 trans_type : 4; + u8 rsv0 : 4; + u8 vl; + u8 rsv1[2]; +}; + +struct ubase_ctrlq_tp_fd_req { + __le32 tpn : 24; + __le32 rsv : 8; +}; + +struct ubase_ctrlq_query_vl_resp { + __le16 vl_bitmap; + u8 rsv[18]; +}; + +struct ubase_ctrlq_query_vl_req { + u8 rsv[20]; +}; + +struct ubase_ctrlq_query_sl_resp { + __le16 unic_sl_bitmap; + __le16 udma_sl_bitmap; + u8 rsv[16]; +}; + +struct ubase_ctrlq_query_sl_req { + u8 rsv[20]; +}; + +struct ubase_ctrlq_chan_ctrl_req { + u8 opc; + u8 rsv[3]; +}; + +struct ubase_ctrlq_reset_ctrl_req { + u8 flag; + u8 rsv[3]; +}; + +int ubase_ctrlq_init(struct ubase_dev *udev); +void ubase_ctrlq_uninit(struct ubase_dev *udev); +void ubase_ctrlq_disable(struct ubase_dev *udev); + +int __ubase_ctrlq_send(struct ubase_dev *udev, struct ubase_ctrlq_msg *msg, + struct ubase_ctrlq_ue_info *ue_info); + +void ubase_ctrlq_service_task(struct ubase_delay_work *ubase_work); +void ubase_ctrlq_handle_crq_msg(struct ubase_dev *udev, + struct ubase_ctrlq_base_block *head, + u16 seq, void *msg, u16 data_len); +void ubase_ctrlq_clean_service_task(struct ubase_delay_work *ubase_work); +void ubase_ctrlq_disable_remote(struct ubase_dev *udev); + +#endif diff --git a/drivers/ub/ubase/ubase_ctrlq_tp.c b/drivers/ub/ubase/ubase_ctrlq_tp.c new file mode 100644 index 0000000000000000000000000000000000000000..5ebcb609a205f74c8208e4b07f0ac888b2553341 --- /dev/null +++ b/drivers/ub/ubase/ubase_ctrlq_tp.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "ubase_ctrlq.h" +#include "ubase_dev.h" +#include "ubase_tp.h" +#include "ubase_ctrlq_tp.h" + +#define UBASE_TRANS_TYPE_UM_TP 0x2 +#define UBASE_TPG_FLAG 0x1 + +int ubase_notify_tp_fd_by_ctrlq(struct ubase_dev *udev, u32 tpn) +{ + struct ubase_tpg *tpg = udev->tp_ctx.tpg; + struct ubase_ctrlq_tp_fd_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + int ret, tmp_resp; + u32 i; + + spin_lock(&udev->tp_ctx.tpg_lock); + if (!tpg) { + spin_unlock(&udev->tp_ctx.tpg_lock); + ubase_warn(udev, + "ubase tpg res does not exist, tpn = %u.\n", tpn); + return 0; + } + + for (i = 0; i < udev->caps.unic_caps.tpg.max_cnt; i++) { + if (tpn >= tpg[i].start_tpn && + tpn < tpg[i].start_tpn + tpg[i].tp_cnt) { + ubase_dbg(udev, + "receive tp flush done AE, tpn:%u, tpgn:%u.\n", + tpn, i); + break; + } + } + spin_unlock(&udev->tp_ctx.tpg_lock); + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_TP_ACL; + msg.opcode = UBASE_CTRLQ_OPC_TP_FLUSH_DONE; + msg.need_resp = 1; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(tmp_resp); + msg.out = &tmp_resp; + req.tpn = cpu_to_le32(tpn); + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) + ubase_err(udev, "failed to notify tp flush done, ret = %d.\n", + ret); + + spin_lock(&udev->tp_ctx.tpg_lock); + if (udev->tp_ctx.tpg && i < udev->caps.unic_caps.tpg.max_cnt) + atomic_inc(&tpg[i].tp_fd_cnt); + else + ubase_warn(udev, + "ubase tpg res does not exist, tpn = %u.\n", tpn); + spin_unlock(&udev->tp_ctx.tpg_lock); + + return ret; +} + +static int ubase_create_tp_tpg_by_ctrlq(struct ubase_dev *udev, u32 vl) +{ + struct ubase_tp_layer_ctx *tp_ctx = &udev->tp_ctx; + struct ubase_ctrlq_create_tp_resp resp = {0}; + struct ubase_ctrlq_create_tp_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + int ret; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_TP_ACL; + msg.opcode = UBASE_CTRLQ_OPC_CREATE_TP; + msg.need_resp = 1; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + + req.trans_type = UBASE_TRANS_TYPE_UM_TP; + req.vl = (u8)vl; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret && ret != -EEXIST) { + ubase_err(udev, "failed to alloc tp tpg, ret = %d.\n", ret); + return ret; + } + + tp_ctx->tpg[vl].mb_tpgn = le32_to_cpu(resp.tpgn); + tp_ctx->tpg[vl].start_tpn = le32_to_cpu(resp.start_tpn); + tp_ctx->tpg[vl].tp_cnt = resp.tpn_cnt; + + if (tp_ctx->tpg[vl].mb_tpgn != vl) + ubase_warn(udev, "unexpected tpgn, vl = %u, tpgn = %u.\n", + vl, tp_ctx->tpg[vl].mb_tpgn); + + return 0; +} + +static void ubase_wait_tp_flush_done_by_ctrlq(struct ubase_dev *udev, u32 vl) +{ + struct ubase_tpg *tpg = &udev->tp_ctx.tpg[vl]; + int i; + + for (i = 0; i < UBASE_WAIT_TP_FLUSH_TOTAL_STEPS; i++) { + msleep(1 << i); + + if (atomic_read(&tpg->tp_fd_cnt) == tpg->tp_cnt) + return; + } + + ubase_warn(udev, + "wait tp flush done timeout, tpgn = %u, tp_fd_cnt = %u.\n", + vl, atomic_read(&tpg->tp_fd_cnt)); +} + +static void ubase_destroy_tp_tpg_by_ctrlq(struct ubase_dev *udev, u32 vl) +{ + struct ubase_ctrlq_destroy_tp_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + int tmp_resp, ret; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_TP_ACL; + msg.opcode = UBASE_CTRLQ_OPC_DESTROY_TP; + msg.need_resp = 1; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(tmp_resp); + msg.out = &tmp_resp; + + req.vl = (u8)vl; + req.trans_type = UBASE_TRANS_TYPE_UM_TP; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) { + ubase_err(udev, + "failed to send destroy tp tpg request, tpgn = %u, ret = %d.\n", + vl, ret); + return; + } + + ubase_wait_tp_flush_done_by_ctrlq(udev, vl); +} + +static void ubase_destroy_multi_tp_tpg_by_ctrlq(struct ubase_dev *udev, u32 num) +{ + u32 idx; + + for (idx = 0; idx < num; idx++) + ubase_destroy_tp_tpg_by_ctrlq(udev, idx); +} + +static int ubase_create_multi_tp_tpg_by_ctrlq(struct ubase_dev *udev) +{ + int ret; + u32 i; + + for (i = 0; i < udev->caps.unic_caps.tpg.max_cnt; i++) { + atomic_set(&udev->tp_ctx.tpg[i].tp_fd_cnt, 0); + ret = ubase_create_tp_tpg_by_ctrlq(udev, i); + if (ret) { + ubase_err(udev, "failed to create tp tpg, tpgn = %u, ret = %d.\n", + i, ret); + goto err_create_tp_tpg; + } + } + + return 0; + +err_create_tp_tpg: + ubase_destroy_multi_tp_tpg_by_ctrlq(udev, i); + + return ret; +} + +void ubase_dev_uninit_rack_tp_tpg(struct ubase_dev *udev) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + struct ubase_tp_layer_ctx *tp_ctx = &udev->tp_ctx; + u32 num = unic_caps->tpg.max_cnt; + + if (!tp_ctx->tpg) + return; + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + ubase_destroy_multi_tp_tpg_by_ctrlq(udev, num); + + spin_lock(&tp_ctx->tpg_lock); + devm_kfree(udev->dev, tp_ctx->tpg); + tp_ctx->tpg = NULL; + spin_unlock(&tp_ctx->tpg_lock); +} + +int ubase_dev_init_rack_tp_tpg(struct ubase_dev *udev) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + struct ubase_tp_layer_ctx *tp_ctx = &udev->tp_ctx; + int ret; + + spin_lock(&tp_ctx->tpg_lock); + tp_ctx->tpg = devm_kcalloc(udev->dev, unic_caps->tpg.max_cnt, + sizeof(struct ubase_tpg), GFP_ATOMIC); + if (!tp_ctx->tpg) { + spin_unlock(&tp_ctx->tpg_lock); + return -ENOMEM; + } + spin_unlock(&tp_ctx->tpg_lock); + + ret = ubase_create_multi_tp_tpg_by_ctrlq(udev); + if (ret) { + spin_lock(&tp_ctx->tpg_lock); + devm_kfree(udev->dev, tp_ctx->tpg); + tp_ctx->tpg = NULL; + spin_unlock(&tp_ctx->tpg_lock); + } + + return ret; +} diff --git a/drivers/ub/ubase/ubase_ctrlq_tp.h b/drivers/ub/ubase/ubase_ctrlq_tp.h new file mode 100644 index 0000000000000000000000000000000000000000..3d647d5d1625c905d1824818037e70be6d2e32bf --- /dev/null +++ b/drivers/ub/ubase/ubase_ctrlq_tp.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_CTRLQ_TP_H__ +#define __UBASE_CTRLQ_TP_H__ + +#include "ubase_dev.h" + +int ubase_notify_tp_fd_by_ctrlq(struct ubase_dev *udev, u32 tp_num); +void ubase_dev_uninit_rack_tp_tpg(struct ubase_dev *udev); +int ubase_dev_init_rack_tp_tpg(struct ubase_dev *udev); + +#endif diff --git a/drivers/ub/ubase/ubase_dev.c b/drivers/ub/ubase/ubase_dev.c new file mode 100644 index 0000000000000000000000000000000000000000..3921cd0ff824e8c0b459728b6a1ff6e5cc8f6929 --- /dev/null +++ b/drivers/ub/ubase/ubase_dev.c @@ -0,0 +1,1403 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include + +#include "debugfs/ubase_debugfs.h" +#include "ubase_arq.h" +#include "ubase_cmd.h" +#include "ubase_ctrlq.h" +#include "ubase_err_handle.h" +#include "ubase_hw.h" +#include "ubase_mailbox.h" +#include "ubase_pmem.h" +#include "ubase_reset.h" +#include "ubase_stats.h" +#include "ubase_dev.h" + +#define UBASE_PERIOD_100MS 100 + +static int ubase_debug; +module_param_named(debug, ubase_debug, int, 0644); +MODULE_PARM_DESC(debug, "enable ubase debug log, 0:disable, others:enable, default:0"); + +static DEFINE_IDA(ubase_adev_ida); + +bool ubase_dev_urma_supported(struct ubase_dev *udev) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + + switch (uent_device(ue)) { + case UBASE_DEV_ID_K_0_URMA_MUE: + case UBASE_DEV_ID_K_0_URMA_UE: + case UBASE_DEV_ID_A_0_URMA_MUE: + case UBASE_DEV_ID_A_0_URMA_UE: + case UBASE_DEV_ID_A_0_UBOE_MUE: + case UBASE_DEV_ID_A_0_UBOE_UE: + break; + default: + return false; + } + + return true; +} + +bool ubase_dev_unic_supported(struct ubase_dev *udev) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + + switch (uent_device(ue)) { + case UBASE_DEV_ID_K_0_URMA_MUE: + case UBASE_DEV_ID_A_0_URMA_MUE: + case UBASE_DEV_ID_A_0_UBOE_MUE: + break; + default: + return false; + } + + return !ubase_get_cap_bit(udev, UBASE_SUPPORT_UNIC_DISABLE_B); +} + +bool ubase_dev_cdma_supported(struct ubase_dev *udev) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + + switch (uent_device(ue)) { + case UBASE_DEV_ID_K_0_CDMA_MUE: + case UBASE_DEV_ID_K_0_CDMA_UE: + case UBASE_DEV_ID_A_0_CDMA_MUE: + case UBASE_DEV_ID_A_0_CDMA_UE: + break; + default: + return false; + } + + return true; +} + +bool ubase_dev_pmu_supported(struct ubase_dev *udev) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + + switch (uent_device(ue)) { + case UBASE_DEV_ID_K_0_PMU_MUE: + case UBASE_DEV_ID_K_0_PMU_UE: + case UBASE_DEV_ID_A_0_PMU_MUE: + case UBASE_DEV_ID_A_0_PMU_UE: + break; + default: + return false; + } + + return true; +} + +bool ubase_dev_fwctl_supported(struct ubase_dev *udev) +{ + return ubase_dev_pmu_supported(udev); +} + +static struct ubase_adev_device { + const char *suffix; + bool (*is_supported)(struct ubase_dev *dev); +} ubase_adev_devices[UBASE_DRV_MAX] = { + [UBASE_DRV_UNIC] = { + .suffix = "unic", + .is_supported = &ubase_dev_unic_supported + }, + [UBASE_DRV_UDMA] = { + .suffix = "udma", + .is_supported = &ubase_dev_udma_supported + }, + [UBASE_DRV_CDMA] = { + .suffix = "cdma", + .is_supported = &ubase_dev_cdma_supported + }, + [UBASE_DRV_FWCTL] = { + .suffix = "fwctl", + .is_supported = &ubase_dev_fwctl_supported + }, + [UBASE_DRV_PMU] = { + .suffix = "pmu", + .is_supported = &ubase_dev_pmu_supported + }, + [UBASE_DRV_UVB] = { + .suffix = "uvb", + .is_supported = &ubase_dev_uvb_supported + }, +}; + +int ubase_adev_idx_alloc(void) +{ + return ida_alloc(&ubase_adev_ida, GFP_KERNEL); +} + +void ubase_adev_idx_free(int id) +{ + ida_free(&ubase_adev_ida, id); +} + +static void ubase_port_handler(struct ubase_dev *udev, bool link_up) +{ + struct ubase_adev *uadev; + int i; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits)) + return; + + mutex_lock(&udev->priv.uadev_lock); + for (i = 0; i < UBASE_DRV_MAX; i++) { + uadev = udev->priv.uadev[i]; + if (!uadev) + continue; + + mutex_lock(&uadev->port_lock); + if (uadev->port_handler) + uadev->port_handler(&uadev->adev, link_up); + mutex_unlock(&uadev->port_lock); + } + mutex_unlock(&udev->priv.uadev_lock); +} + +void ubase_port_down(struct ubase_dev *udev) +{ + ubase_port_handler(udev, 0); +} + +void ubase_port_up(struct ubase_dev *udev) +{ + ubase_port_handler(udev, 1); +} + +static void ubase_comm_adev_release(struct device *dev) +{ + struct ubase_adev *ubase_adev = + container_of(dev, struct ubase_adev, adev.dev); + + kfree(ubase_adev); +} + +static struct ubase_adev *ubase_add_one_adev(struct ubase_dev *udev, int idx) +{ + struct ubase_adev *uadev; + int ret; + + uadev = kzalloc(sizeof(struct ubase_adev), GFP_KERNEL); + if (!uadev) { + ubase_err(udev, "failed to alloc auxiliary device(%s.%d).\n", + ubase_adev_devices[idx].suffix, udev->dev_id); + return ERR_PTR(-ENOMEM); + } + + uadev->adev.name = ubase_adev_devices[idx].suffix; + uadev->adev.id = (u32)udev->dev_id; + uadev->adev.dev.parent = udev->dev; + uadev->adev.dev.release = ubase_comm_adev_release; + uadev->idx = idx; + uadev->udev = udev; + ATOMIC_INIT_NOTIFIER_HEAD(&uadev->comp_nh); + + ret = auxiliary_device_init(&uadev->adev); + if (ret) { + kfree(uadev); + ubase_err(udev, + "failed to init auxiliary device(%s.%d), ret = %d\n", + ubase_adev_devices[idx].suffix, udev->dev_id, ret); + return ERR_PTR(ret); + } + + ret = auxiliary_device_add(&uadev->adev); + if (ret) { + auxiliary_device_uninit(&uadev->adev); + ubase_err(udev, + "failed to add auxiliary device(%s.%d), ret = %d\n", + ubase_adev_devices[idx].suffix, udev->dev_id, ret); + return ERR_PTR(ret); + } + + mutex_init(&uadev->virt_lock); + mutex_init(&uadev->port_lock); + mutex_init(&uadev->reset_lock); + mutex_init(&uadev->activate_lock); + + return uadev; +} + +static void ubase_del_one_adev(struct ubase_dev *udev, int idx) +{ + struct ubase_priv *priv = &udev->priv; + struct ubase_adev *uadev; + + uadev = priv->uadev[idx]; + + mutex_destroy(&uadev->activate_lock); + mutex_destroy(&uadev->reset_lock); + mutex_destroy(&uadev->port_lock); + mutex_destroy(&uadev->virt_lock); + auxiliary_device_delete(&uadev->adev); + auxiliary_device_uninit(&uadev->adev); + priv->uadev[idx] = NULL; +} + +static int ubase_init_aux_devices(struct ubase_dev *udev) +{ + struct ubase_priv *priv = &udev->priv; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ubase_adev_devices); i++) { + if (priv->uadev[i]) + continue; + + if (!ubase_adev_devices[i].is_supported || + !ubase_adev_devices[i].is_supported(udev)) + continue; + + priv->uadev[i] = ubase_add_one_adev(udev, i); + if (IS_ERR(priv->uadev[i])) { + ret = PTR_ERR(priv->uadev[i]); + priv->uadev[i] = NULL; + ubase_err(udev, + "failed to load auxiliary device(%s.%d)\n", + ubase_adev_devices[i].suffix, udev->dev_id); + goto err_add_aux_dev; + } + } + + mutex_init(&udev->priv.uadev_lock); + + return 0; + +err_add_aux_dev: + for (; i >= 0; i--) { + if (!priv->uadev[i]) + continue; + + ubase_del_one_adev(udev, i); + } + + return ret; +} + +static void ubase_uninit_aux_devices(struct ubase_dev *udev) +{ + struct ubase_priv *priv = &udev->priv; + int i; + + mutex_lock(&priv->uadev_lock); + for (i = ARRAY_SIZE(ubase_adev_devices) - 1; i >= 0; i--) { + if (!priv->uadev[i]) + continue; + + ubase_del_one_adev(udev, i); + } + mutex_unlock(&priv->uadev_lock); + + mutex_destroy(&udev->priv.uadev_lock); +} + +static void ubase_cancel_period_service_task(struct ubase_dev *udev) +{ + if (udev->period_service_task.service_task.work.func) + cancel_delayed_work_sync(&udev->period_service_task.service_task); +} + +static int ubase_enable_period_service_task(struct ubase_dev *udev) +{ + struct ubase_delay_work *period_work = &udev->period_service_task; + unsigned long delta; + + delta = round_jiffies_relative(msecs_to_jiffies(UBASE_PERIOD_100MS)); + mod_delayed_work(udev->ubase_period_wq, + &period_work->service_task, + delta); + + return 0; +} + +static void ubase_period_service_task(struct work_struct *work) +{ +#define UBASE_STATS_TIMER_INTERVAL (300000 / (UBASE_PERIOD_100MS)) +#define UBASE_QUERY_SL_TIMER_INTERVAL (1000 / (UBASE_PERIOD_100MS)) + + struct ubase_delay_work *ubase_work = + container_of(work, struct ubase_delay_work, service_task.work); + struct ubase_dev *udev = container_of(ubase_work, struct ubase_dev, + period_service_task); + + if (test_bit(UBASE_STATE_DISABLED_B, &udev->state_bits)) { + ubase_enable_period_service_task(udev); + return; + } + + udev->serv_proc_cnt++; + ubase_enable_period_service_task(udev); +} + +static void ubase_arq_service_task(struct work_struct *work) +{ + struct ubase_delay_work *ubase_work = + container_of(work, struct ubase_delay_work, service_task.work); + + ubase_cmd_arq_handler(ubase_work); +} + +static void ubase_reset_service_task(struct work_struct *work) +{ + struct ubase_delay_work *ubase_work = + container_of(work, struct ubase_delay_work, service_task.work); + + ubase_reset_service(ubase_work); +} + +static void ubase_service_task(struct work_struct *work) +{ + struct ubase_delay_work *ubase_work = + container_of(work, struct ubase_delay_work, service_task.work); + + ubase_crq_service_task(ubase_work); + ubase_errhandle_service_task(ubase_work); + ubase_ctrlq_service_task(ubase_work); + ubase_ctrlq_clean_service_task(ubase_work); +} + +static void ubase_init_delayed_work(struct ubase_dev *udev) +{ + INIT_DELAYED_WORK(&udev->service_task.service_task, ubase_service_task); + INIT_DELAYED_WORK(&udev->reset_service_task.service_task, + ubase_reset_service_task); + INIT_DELAYED_WORK(&udev->period_service_task.service_task, + ubase_period_service_task); + INIT_DELAYED_WORK(&udev->arq_service_task.service_task, + ubase_arq_service_task); +} + +static int ubase_wq_init(struct ubase_dev *udev) +{ +#define UBASE_ALLOC_WQ(name) alloc_workqueue("%s", WQ_UNBOUND, 0, name) + + udev->ubase_wq = UBASE_ALLOC_WQ("ubase"); + if (!udev->ubase_wq) { + ubase_err(udev, "failed to alloc ubase workqueue.\n"); + goto err_alloc_ubase_wq; + } + + udev->ubase_async_wq = UBASE_ALLOC_WQ("ubase_async_service"); + if (!udev->ubase_async_wq) { + ubase_err(udev, "failed to alloc ubase async workqueue.\n"); + goto err_alloc_ubase_async_wq; + } + + udev->ubase_reset_wq = UBASE_ALLOC_WQ("ubase_reset_service"); + if (!udev->ubase_reset_wq) { + ubase_err(udev, "failed to alloc ubase reset workqueue.\n"); + goto err_alloc_ubase_reset_wq; + } + + udev->ubase_period_wq = UBASE_ALLOC_WQ("ubase_period_service"); + if (!udev->ubase_period_wq) { + ubase_err(udev, "failed to alloc ubase period workqueue.\n"); + goto err_alloc_ubase_period_wq; + } + + udev->ubase_arq_wq = UBASE_ALLOC_WQ("ubase_arq_service"); + if (!udev->ubase_arq_wq) { + ubase_err(udev, "failed to alloc ubase arq workqueue.\n"); + goto err_alloc_ubase_arq_wq; + } + + ubase_init_delayed_work(udev); + return 0; + +err_alloc_ubase_arq_wq: + destroy_workqueue(udev->ubase_period_wq); +err_alloc_ubase_period_wq: + destroy_workqueue(udev->ubase_reset_wq); +err_alloc_ubase_reset_wq: + destroy_workqueue(udev->ubase_async_wq); +err_alloc_ubase_async_wq: + destroy_workqueue(udev->ubase_wq); +err_alloc_ubase_wq: + return -ENOMEM; +} + +static void ubase_wq_uninit(struct ubase_dev *udev) +{ + destroy_workqueue(udev->ubase_arq_wq); + destroy_workqueue(udev->ubase_period_wq); + destroy_workqueue(udev->ubase_reset_wq); + destroy_workqueue(udev->ubase_async_wq); + destroy_workqueue(udev->ubase_wq); +} + +static int ubase_handle_ue2ue_ctrlq_req(struct ubase_dev *udev, + struct ubase_ue2ue_ctrlq_head *cmd, + u32 len) +{ + struct ubase_ctrlq_base_block *head = (struct ubase_ctrlq_base_block *)(cmd + 1); + u16 mbx_ue_id = le16_to_cpu(cmd->head.mbx_ue_id); + struct ubase_ctrlq_ue_info ue_info; + struct ubase_ctrlq_msg msg = {0}; + int ret; + + if (!ubase_mbx_ue_id_is_valid(mbx_ue_id, udev)) { + ubase_err(udev, "ubase ue2ue ctrlq req mbx ue id = %u error.\n", + mbx_ue_id); + return -EINVAL; + } + + if (cmd->in_size > (len - (sizeof(*cmd) + UBASE_CTRLQ_HDR_LEN))) { + ubase_err(udev, "ubase ue2ue cmd len = %u error.\n", cmd->in_size); + return -EINVAL; + } + + if (cmd->in_size > (len - (sizeof(*cmd) + UBASE_CTRLQ_HDR_LEN))) { + ubase_err(udev, "ubase e2e cmd len = %u error.\n", cmd->in_size); + return -EINVAL; + } + + msg.service_ver = head->service_ver; + msg.service_type = head->service_type; + msg.opcode = head->opcode; + msg.need_resp = cmd->need_resp; + msg.is_resp = cmd->is_resp; + msg.resp_seq = cmd->seq; + msg.in = (u8 *)head + UBASE_CTRLQ_HDR_LEN; + msg.in_size = cmd->in_size; + msg.out = NULL; + msg.out_size = 0; + + ue_info.bus_ue_id = le16_to_cpu(cmd->head.bus_ue_id); + ue_info.seq = cmd->seq; + ue_info.mbx_ue_id = mbx_ue_id; + + ret = __ubase_ctrlq_send(udev, &msg, &ue_info); + if (ret) + ubase_err(udev, "failed to send opc(0x%x) ctrlq, ret = %d.\n", + head->opcode, ret); + + return ret; +} + +static int ubase_handle_ue2ue_ctrlq_event(struct ubase_dev *udev, void *data, + u32 len) +{ + struct ubase_ue2ue_ctrlq_head *cmd = data; + struct ubase_ctrlq_base_block *head; + u16 data_len; + + if (len < (sizeof(*cmd) + UBASE_CTRLQ_HDR_LEN)) { + ubase_err(udev, "invalid ue2ue ctrlq event len(%u).\n", len); + return -EINVAL; + } + + if (ubase_dev_ctrlq_supported(udev)) + return ubase_handle_ue2ue_ctrlq_req(udev, cmd, len); + + head = (struct ubase_ctrlq_base_block *)(cmd + 1); + data_len = len - sizeof(*cmd) - UBASE_CTRLQ_HDR_LEN; + ubase_ctrlq_handle_crq_msg(udev, head, cmd->seq, + (u8 *)head + UBASE_CTRLQ_HDR_LEN, data_len); + + return 0; +} + +struct ubase_ue2ue_event_handler { + u16 sub_cmd; + int (*event_handler)(struct ubase_dev *udev, void *data, u32 len); +} ubase_ue2ue_events[] = { + { UBASE_UE2UE_CTRLQ_MSG, ubase_handle_ue2ue_ctrlq_event }, +}; + +static int ubase_handle_ue2ue_event(void *dev, void *data, u32 len) +{ + struct ubase_ue2ue_common_head *head = data; + struct ubase_dev *udev = dev; + int i; + + for (i = 0; i < ARRAY_SIZE(ubase_ue2ue_events); i++) { + if (ubase_ue2ue_events[i].sub_cmd == head->sub_cmd) + return ubase_ue2ue_events[i].event_handler(udev, data, + len); + } + + ubase_warn(udev, "unknown ubase ue2ue event, sub_cmd = %u.\n", + head->sub_cmd); + + return 0; +} + +static int ubase_handle_ue_reset_event(void *dev, void *data, u32 len) +{ + struct ubase_dev *udev = dev; + + ubase_info(udev, "recv ue reset req, will reset.\n"); + __ubase_reset_event(udev, UBASE_UE_RESET); + + return 0; +} + +static int ubase_handle_activate_resp(void *dev, void *data, u32 len) +{ + struct ubase_activate_resp *resp = data; + struct ubase_act_info *self, *other; + struct ubase_dev *udev = dev; + u16 msn; + + if (len != sizeof(*resp)) { + ubase_err(udev, + "activate dev resp len error, cur = %u, expect = %lu.\n", + len, sizeof(*resp)); + return -EINVAL; + } + + msn = le16_to_cpu(resp->msn); + self = &udev->act_ctx.self; + if (self->wait_msn == msn) { + self->result = -resp->result; + complete(&self->activate_done); + return 0; + } + other = &udev->act_ctx.other; + if (other->wait_msn == msn) { + other->result = -resp->result; + complete(&other->activate_done); + return 0; + } + + ubase_warn(udev, + "unknown msn in activate resp, msn = %u, self msn = %u, other msn = %u.\n", + msn, self->wait_msn, other->wait_msn); + + return -EIO; +} +static struct ubase_crq_event_nb ubase_crq_events[] = { + { + .opcode = UBASE_OPC_UE2UE_UBASE, + .crq_handler = ubase_handle_ue2ue_event, + }, + { + .opcode = UBASE_OPC_NOTIFY_UE_RESET, + .crq_handler = ubase_handle_ue_reset_event, + }, + { + .opcode = UBASE_OPC_ACTIVATE_RESP, + .crq_handler = ubase_handle_activate_resp, + }, +}; + +static void ubase_unregister_cmdq_crq_event(struct ubase_dev *udev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ubase_crq_events); i++) + __ubase_unregister_crq_event(udev, ubase_crq_events[i].opcode); +} + +static int ubase_register_cmdq_crq_event(struct ubase_dev *udev) +{ + int ret, i; + + for (i = 0; i < ARRAY_SIZE(ubase_crq_events); i++) { + ubase_crq_events[i].back = udev; + + ret = __ubase_register_crq_event(udev, &ubase_crq_events[i]); + if (ret) { + ubase_err(udev, + "failed to register crq event[%d], ret = %d.\n", + i, ret); + goto err_reg_event; + } + } + + return 0; + +err_reg_event: + for (i = i - 1; i >= 0; i--) + __ubase_unregister_crq_event(udev, ubase_crq_events[i].opcode); + return ret; +} + +static const struct ubase_init_function ubase_init_func_map[] = { + { + "init work queue", UBASE_SUP_ALL, 0, + ubase_wq_init, ubase_wq_uninit + }, + { + "init cmd queue", UBASE_SUP_ALL, 1, + ubase_cmd_init, ubase_cmd_uninit + }, + { + "query dev res", UBASE_SUP_ALL, 0, + ubase_query_dev_res, NULL + }, + { + "init mailbox", UBASE_SUP_NO_PMU, 0, + ubase_mbox_cmd_init, ubase_mbox_cmd_uninit + }, + { + "query chip info", UBASE_SUP_ALL, 0, + ubase_query_chip_info, NULL + }, + { + "query controller_info", UBASE_SUP_NO_PMU, 0, + ubase_query_controller_info, NULL + }, + { + "query hw oor caps", UBASE_SUP_NO_PMU, 0, + ubase_query_hw_oor_caps, NULL + }, + { + "query port bitmap", UBASE_SUP_ALL, 0, + ubase_query_port_bitmap, NULL + }, + { + "init irq table", UBASE_SUP_NO_PMU, 1, + ubase_irq_table_init, ubase_irq_table_uninit + }, + { + "init ctrl queue", UBASE_SUP_NO_PMU, 1, + ubase_ctrlq_init, ubase_ctrlq_uninit + }, + { + "register aeq event", UBASE_SUP_NO_PMU, 0, + ubase_register_ae_event, ubase_unregister_ae_event + }, + { + "register cmdq crq event", UBASE_SUP_NO_PMU, 0, + ubase_register_cmdq_crq_event, ubase_unregister_cmdq_crq_event + }, + { + "register ctrlq crq event", UBASE_SUP_NO_PMU, 0, + NULL, NULL + }, + { + "init qos", UBASE_SUP_ALL, 0, + ubase_qos_init, NULL + }, + { + "prealloc memory", UBASE_SUP_UDMA, 1, + ubase_prealloc_mem_init, ubase_prealloc_mem_uninit + }, + { + "init ue", UBASE_SUP_NO_PMU, 0, + ubase_ue_init, ubase_ue_uninit + }, + { + "init hw", UBASE_SUP_NO_PMU, 1, + ubase_hw_init, ubase_hw_uninit + }, + { + "init debugfs", UBASE_SUP_ALL, 0, + ubase_dbg_init, ubase_dbg_uninit + }, + { + "init auxiliary devices", UBASE_SUP_ALL, 0, + ubase_init_aux_devices, ubase_uninit_aux_devices + }, + { + "enable period service task", UBASE_SUP_NO_PMU, 0, + ubase_enable_period_service_task, ubase_cancel_period_service_task + }, + { + "enable ce irq", UBASE_SUP_NO_PMU, 1, + ubase_enable_ce_irqs, ubase_disable_ce_irqs + }, +}; + +static bool ubase_init_func_support(struct ubase_dev *udev, u32 support) +{ + return (((support & UBASE_SUP_UNIC) && ubase_dev_unic_supported(udev)) || + ((support & UBASE_SUP_UDMA) && ubase_dev_udma_supported(udev)) || + ((support & UBASE_SUP_CDMA) && ubase_dev_cdma_supported(udev)) || + ((support & UBASE_SUP_PMU) && ubase_dev_pmu_supported(udev))); +} + +int ubase_dev_init(struct ubase_dev *udev) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ubase_init_func_map); i++) { + if (!ubase_init_func_support(udev, + ubase_init_func_map[i].support_devs)) + continue; + + if (ubase_init_func_map[i].init_func) { + ret = ubase_init_func_map[i].init_func(udev); + if (ret) { + ubase_err(udev, "failed to %s, ret = %d.\n", + ubase_init_func_map[i].err_msg, ret); + goto err_init; + } + } + } + + set_bit(UBASE_STATE_INITED_B, &udev->state_bits); + + return 0; + +err_init: + for (i -= 1; i >= 0; i--) { + if (!ubase_init_func_support(udev, + ubase_init_func_map[i].support_devs)) + continue; + + if (ubase_init_func_map[i].uninit_func) + ubase_init_func_map[i].uninit_func(udev); + } + + return ret; +} + +void ubase_dev_uninit(struct ubase_dev *udev) +{ + int i; + + if (udev->service_task.service_task.work.func) + cancel_delayed_work_sync(&udev->service_task.service_task); + flush_workqueue(udev->ubase_async_wq); + + for (i = ARRAY_SIZE(ubase_init_func_map) - 1; i >= 0; i--) { + if (!ubase_init_func_support(udev, + ubase_init_func_map[i].support_devs)) + continue; + + if (ubase_init_func_map[i].uninit_func) + ubase_init_func_map[i].uninit_func(udev); + } +} + +static void __ubase_reset_uninit(struct ubase_dev *udev, int end) +{ + int i; + + for (i = end; i >= 0; i--) { + if (!ubase_init_func_support(udev, + ubase_init_func_map[i].support_devs)) + continue; + + if (ubase_init_func_map[i].uninit_func && + ubase_init_func_map[i].need_reset) + ubase_init_func_map[i].uninit_func(udev); + } +} + +int ubase_dev_reset_init(struct ubase_dev *udev) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ubase_init_func_map); i++) { + if (!ubase_init_func_map[i].need_reset) + continue; + + if (!ubase_init_func_support(udev, + ubase_init_func_map[i].support_devs)) + continue; + + if (!ubase_init_func_map[i].init_func) + continue; + + ret = ubase_init_func_map[i].init_func(udev); + if (ret) { + ubase_err(udev, "failed to %s, ret = %d.\n", + ubase_init_func_map[i].err_msg, ret); + goto err_init; + } + } + + return 0; + +err_init: + __ubase_reset_uninit(udev, i - 1); + + return ret; +} + +void ubase_dev_reset_uninit(struct ubase_dev *udev) +{ + __ubase_reset_uninit(udev, ARRAY_SIZE(ubase_init_func_map) - 1); +} + +void ubase_suspend_aux_devices(struct ubase_dev *udev) +{ + struct ubase_priv *priv = &udev->priv; + struct ubase_adev *uadev; + int i; + + mutex_lock(&priv->uadev_lock); + for (i = ARRAY_SIZE(ubase_adev_devices) - 1; i >= 0; i--) { + uadev = priv->uadev[i]; + if (!uadev) + continue; + + mutex_lock(&uadev->reset_lock); + if (uadev->reset_handler) + uadev->reset_handler(&uadev->adev, udev->reset_stage); + mutex_unlock(&uadev->reset_lock); + } + mutex_unlock(&priv->uadev_lock); +} + +void ubase_resume_aux_devices(struct ubase_dev *udev) +{ + struct ubase_priv *priv = &udev->priv; + struct ubase_adev *uadev; + int i; + + mutex_lock(&priv->uadev_lock); + for (i = 0; i < ARRAY_SIZE(ubase_adev_devices); i++) { + uadev = priv->uadev[i]; + if (!uadev) + continue; + + mutex_lock(&uadev->reset_lock); + if (uadev->reset_handler) + uadev->reset_handler(&uadev->adev, + UBASE_RESET_STAGE_INIT); + mutex_unlock(&uadev->reset_lock); + } + mutex_unlock(&priv->uadev_lock); +} + +bool ubase_adev_ubl_supported(struct auxiliary_device *adev) +{ + if (!adev) + return false; + + return ubase_dev_ubl_supported(__ubase_get_udev_by_adev(adev)); +} +EXPORT_SYMBOL(ubase_adev_ubl_supported); + +bool ubase_adev_ctrlq_supported(struct auxiliary_device *adev) +{ + if (!adev) + return false; + + return ubase_dev_ctrlq_supported(__ubase_get_udev_by_adev(adev)); +} +EXPORT_SYMBOL(ubase_adev_ctrlq_supported); + +bool ubase_adev_eth_mac_supported(struct auxiliary_device *adev) +{ + if (!adev) + return false; + + return ubase_dev_eth_mac_supported(__ubase_get_udev_by_adev(adev)); +} +EXPORT_SYMBOL(ubase_adev_eth_mac_supported); + +struct ubase_resource_space *ubase_get_io_base(struct auxiliary_device *adev) +{ + if (!adev) + return NULL; + + return &__ubase_get_udev_by_adev(adev)->hw.io_base; +} +EXPORT_SYMBOL(ubase_get_io_base); + +struct ubase_resource_space *ubase_get_mem_base(struct auxiliary_device *adev) +{ + if (!adev) + return NULL; + + return &__ubase_get_udev_by_adev(adev)->hw.mem_base; +} +EXPORT_SYMBOL(ubase_get_mem_base); + +struct ubase_caps *ubase_get_dev_caps(struct auxiliary_device *adev) +{ + if (!adev) + return NULL; + + return &__ubase_get_udev_by_adev(adev)->caps.dev_caps; +} +EXPORT_SYMBOL(ubase_get_dev_caps); + +struct ubase_adev_caps *ubase_get_udma_caps(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + + if (!adev) + return NULL; + + udev = __ubase_get_udev_by_adev(adev); + + return &udev->caps.udma_caps; +} +EXPORT_SYMBOL(ubase_get_udma_caps); + +struct ubase_adev_caps *ubase_get_cdma_caps(struct auxiliary_device *adev) +{ + return ubase_get_udma_caps(adev); +} +EXPORT_SYMBOL(ubase_get_cdma_caps); + +enum ubase_reset_stage ubase_get_reset_stage(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + + if (!adev) + return UBASE_RESET_STAGE_NONE; + + udev = __ubase_get_udev_by_adev(adev); + + return udev->reset_stage; +} +EXPORT_SYMBOL(ubase_get_reset_stage); + +void ubase_virt_register(struct auxiliary_device *adev, + void (*virt_handler)(struct auxiliary_device *adev, + u16 bus_ue_id, bool is_en)) +{ + struct ubase_adev *uadev; + + if (!adev || !virt_handler) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->virt_lock); + if (!uadev->virt_handler) + uadev->virt_handler = virt_handler; + mutex_unlock(&uadev->virt_lock); +} +EXPORT_SYMBOL(ubase_virt_register); + +void ubase_virt_unregister(struct auxiliary_device *adev) +{ + struct ubase_adev *uadev; + + if (!adev) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->virt_lock); + uadev->virt_handler = NULL; + mutex_unlock(&uadev->virt_lock); +} +EXPORT_SYMBOL(ubase_virt_unregister); + +void ubase_port_register(struct auxiliary_device *adev, + void (*port_handler)(struct auxiliary_device *adev, + bool link_up)) +{ + struct ubase_adev *uadev; + + if (!adev || !port_handler) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->port_lock); + if (!uadev->port_handler) + uadev->port_handler = port_handler; + mutex_unlock(&uadev->port_lock); +} +EXPORT_SYMBOL(ubase_port_register); + +void ubase_port_unregister(struct auxiliary_device *adev) +{ + struct ubase_adev *uadev; + + if (!adev) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->port_lock); + uadev->port_handler = NULL; + mutex_unlock(&uadev->port_lock); +} +EXPORT_SYMBOL(ubase_port_unregister); + +void ubase_reset_register(struct auxiliary_device *adev, + void (*reset_handler)(struct auxiliary_device *adev, + enum ubase_reset_stage stage)) +{ + struct ubase_adev *uadev; + + if (!adev || !reset_handler) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->reset_lock); + if (!uadev->reset_handler) + uadev->reset_handler = reset_handler; + mutex_unlock(&uadev->reset_lock); +} +EXPORT_SYMBOL(ubase_reset_register); + +void ubase_reset_unregister(struct auxiliary_device *adev) +{ + struct ubase_adev *uadev; + + if (!adev) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->reset_lock); + uadev->reset_handler = NULL; + mutex_unlock(&uadev->reset_lock); +} +EXPORT_SYMBOL(ubase_reset_unregister); + +struct ubase_adev_caps *ubase_get_unic_caps(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + + if (!adev) + return NULL; + + udev = __ubase_get_udev_by_adev(adev); + + return &udev->caps.unic_caps; +} +EXPORT_SYMBOL(ubase_get_unic_caps); + +static bool ubase_add_ue_list(struct ubase_dev *udev, u16 bus_ue_id) +{ + struct ubase_ue_node *pos_node, *tmp_node, *new_node; + + new_node = kzalloc(sizeof(*new_node), GFP_KERNEL); + if (!new_node) { + ubase_err(udev, "failed to alloc ue node.\n"); + return false; + } + new_node->bus_ue_id = bus_ue_id; + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry_safe(pos_node, tmp_node, &udev->ue_list, list) { + if (pos_node->bus_ue_id == bus_ue_id) { + kfree(new_node); + mutex_unlock(&udev->ue_list_lock); + return false; + } else if (bus_ue_id < pos_node->bus_ue_id) { + list_add(&new_node->list, pos_node->list.prev); + goto add_ue_end; + } + } + list_add_tail(&new_node->list, &udev->ue_list); + +add_ue_end: + mutex_unlock(&udev->ue_list_lock); + return true; +} + +static bool ubase_del_ue_list(struct ubase_dev *udev, u16 bus_ue_id) +{ + struct ubase_ue_node *pos_node, *tmp_node; + + mutex_lock(&udev->ue_list_lock); + list_for_each_entry_safe(pos_node, tmp_node, &udev->ue_list, list) { + if (pos_node->bus_ue_id == bus_ue_id) { + list_del(&pos_node->list); + kfree(pos_node); + mutex_unlock(&udev->ue_list_lock); + return true; + } + } + mutex_unlock(&udev->ue_list_lock); + + return false; +} + +static bool ubase_modify_ue_list(struct ubase_dev *udev, u16 bus_ue_id, bool is_en) +{ + if (is_en) + return ubase_add_ue_list(udev, bus_ue_id); + else + return ubase_del_ue_list(udev, bus_ue_id); +} + +void ubase_virt_handler(struct ubase_dev *udev, u16 bus_ue_id, bool is_en) +{ + struct ubase_adev *uadev; + int i; + + if (!ubase_modify_ue_list(udev, bus_ue_id, is_en)) + return; + + mutex_lock(&udev->priv.uadev_lock); + for (i = 0; i < UBASE_DRV_MAX; i++) { + uadev = udev->priv.uadev[i]; + if (!uadev) + continue; + + mutex_lock(&uadev->virt_lock); + if (uadev->virt_handler) + uadev->virt_handler(&uadev->adev, bus_ue_id, is_en); + mutex_unlock(&uadev->virt_lock); + } + mutex_unlock(&udev->priv.uadev_lock); +} + +bool ubase_dbg_default(void) +{ + return ubase_debug; +} + +struct ubase_adev_qos *ubase_get_adev_qos(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + + if (!adev) + return NULL; + + udev = __ubase_get_udev_by_adev(adev); + return &udev->qos; +} +EXPORT_SYMBOL(ubase_get_adev_qos); + +static void ubase_activate_notify(struct ubase_dev *udev, + struct auxiliary_device *adev, bool activate) +{ + bool disable_state = test_bit(UBASE_STATE_DISABLED_B, &udev->state_bits); + struct ubase_adev *uadev; + int i; + + if (!disable_state) + mutex_lock(&udev->priv.uadev_lock); + + for (i = 0; i < UBASE_DRV_MAX; i++) { + uadev = udev->priv.uadev[i]; + if (!uadev || &uadev->adev == adev) + continue; + + mutex_lock(&uadev->activate_lock); + if (uadev->activate_handler) + uadev->activate_handler(&uadev->adev, activate); + mutex_unlock(&uadev->activate_lock); + } + + if (!disable_state) + mutex_unlock(&udev->priv.uadev_lock); +} + +void ubase_activate_register(struct auxiliary_device *adev, + void (*activate_handler)(struct auxiliary_device *adev, + bool activate)) +{ + struct ubase_adev *uadev; + + if (!adev || !activate_handler) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->activate_lock); + if (!uadev->activate_handler) + uadev->activate_handler = activate_handler; + mutex_unlock(&uadev->activate_lock); +} +EXPORT_SYMBOL(ubase_activate_register); + +void ubase_activate_unregister(struct auxiliary_device *adev) +{ + struct ubase_adev *uadev; + + if (!adev) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + mutex_lock(&uadev->activate_lock); + uadev->activate_handler = NULL; + mutex_unlock(&uadev->activate_lock); +} +EXPORT_SYMBOL(ubase_activate_unregister); + +static int ubase_wait_activate_done(struct ubase_dev *udev, u16 bus_ue_id) +{ +#define UBASE_ACTIVE_DEV_TIMEOUT 10000 + + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + struct ubase_act_info *info; + + info = (ue->entity_idx == bus_ue_id) ? &udev->act_ctx.self : + &udev->act_ctx.other; + + if (!wait_for_completion_timeout(&info->activate_done, + msecs_to_jiffies(UBASE_ACTIVE_DEV_TIMEOUT))) { + ubase_err(udev, + "wait activate dev resp timeout, bus_ue_id = %u, msn = %u.\n", + bus_ue_id, info->wait_msn); + return -ETIMEDOUT; + } + + return info->result; +} + +static void ubase_record_msn(struct ubase_dev *udev, u16 bus_ue_id, u16 msn) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + struct ubase_act_info *info; + + info = (ue->entity_idx == bus_ue_id) ? &udev->act_ctx.self : + &udev->act_ctx.other; + + info->wait_msn = msn; +} + +static void ubase_alloc_msn(struct ubase_dev *udev, u16 *msn) +{ + struct ubase_act_ctx *ctx = &udev->act_ctx; + + /* we cannot distinguish whether it is a real 0 + * or a 0 caused by the peer not assigning a value, + * so we skip the number 0. + */ + mutex_lock(&ctx->lock); + ++ctx->msn; + if (!ctx->msn) + ctx->msn = 1; + *msn = ctx->msn; + mutex_unlock(&ctx->lock); +} + +static int ubase_send_activate_dev_req(struct ubase_dev *udev, bool activate, + u16 bus_ue_id) +{ + struct ubase_activate_req req = {0}; + struct ubase_cmd_buf in; + u16 msn; + int ret; + + req.activate = activate ? 1 : 0; + req.bus_ue_id = cpu_to_le16(bus_ue_id); + ubase_alloc_msn(udev, &msn); + req.msn = cpu_to_le16(msn); + ubase_record_msn(udev, bus_ue_id, msn); + + ubase_fill_inout_buf(&in, UBASE_OPC_ACTIVATE_REQ, false, sizeof(req), + &req); + ret = __ubase_cmd_send_in(udev, &in); + if (ret) { + ubase_err(udev, + "failed to send activate dev req, ue id=%u, ret=%d.\n", + bus_ue_id, ret); + return ret; + } + + return ubase_wait_activate_done(udev, bus_ue_id); +} + +int ubase_activate_handler(struct ubase_dev *udev, u32 bus_ue_id) +{ + return ubase_send_activate_dev_req(udev, true, (u16)bus_ue_id); +} + +int ubase_deactivate_handler(struct ubase_dev *udev, u32 bus_ue_id) +{ + return ubase_send_activate_dev_req(udev, false, (u16)bus_ue_id); +} + +int ubase_activate_dev(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + struct ub_entity *ue; + int ret; + + if (!adev) + return 0; + + udev = __ubase_get_udev_by_adev(adev); + + ue = container_of(udev->dev, struct ub_entity, dev); + if (ubase_activate_proxy_supported(udev) && + !test_bit(UBASE_STATE_DISABLED_B, &udev->state_bits)) + ret = ub_activate_entity(ue, ue->entity_idx); + else + ret = ubase_activate_handler(udev, ue->entity_idx); + + if (ret) { + ubase_err(udev, + "failed to activate ubase dev, ret = %d.\n", ret); + goto activate_dev_err; + } + + ubase_activate_notify(udev, adev, true); + +activate_dev_err: + ubase_update_activate_stats(udev, true, ret); + + return ret; +} +EXPORT_SYMBOL(ubase_activate_dev); + +int ubase_deactivate_dev(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + struct ub_entity *ue; + int ret; + + if (!adev) + return 0; + + udev = __ubase_get_udev_by_adev(adev); + + ue = container_of(udev->dev, struct ub_entity, dev); + ubase_activate_notify(udev, adev, false); + + if (ubase_activate_proxy_supported(udev) && + !test_bit(UBASE_STATE_DISABLED_B, &udev->state_bits)) + ret = ub_deactivate_entity(ue, ue->entity_idx); + else + ret = ubase_deactivate_handler(udev, ue->entity_idx); + + if (ret) { + ubase_err(udev, + "failed to deactivate ubase dev, ret = %d.\n", ret); + ubase_activate_notify(udev, adev, true); + } + + ubase_update_activate_stats(udev, false, ret); + + return ret; +} +EXPORT_SYMBOL(ubase_deactivate_dev); + +static int ubase_query_bus_eid(struct ubase_dev *udev, struct ubase_bus_eid *eid) +{ + struct ubase_query_ueid_cmd resp = {0}; + struct ubase_cmd_buf in, out; + int i, ret; + + __ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_BUS_EID, true, 0, NULL); + __ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_BUS_EID, false, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, "failed to query bus eid, ret = %d.\n", ret); + return ret; + } + + for (i = 0; i < UBASE_BUS_EID_LEN; ++i) + eid->eid[i] = le32_to_cpu(resp.ueid[i]); + + return 0; +} + +static int __ubase_get_bus_eid(struct ubase_dev *udev, struct ubase_bus_eid *eid) +{ + return ubase_query_bus_eid(udev, eid); +} + +int ubase_get_bus_eid(struct auxiliary_device *adev, struct ubase_bus_eid *eid) +{ + struct ubase_dev *udev; + + if (!adev || !eid) + return -EINVAL; + + udev = __ubase_get_udev_by_adev(adev); + + return __ubase_get_bus_eid(udev, eid); +} +EXPORT_SYMBOL(ubase_get_bus_eid); diff --git a/drivers/ub/ubase/ubase_dev.h b/drivers/ub/ubase/ubase_dev.h new file mode 100644 index 0000000000000000000000000000000000000000..d32d9fb983776b91679c80b712c0d0f46b6d9dfa --- /dev/null +++ b/drivers/ub/ubase/ubase_dev.h @@ -0,0 +1,459 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_DEV_H__ +#define __UBASE_DEV_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ubase.h" +#include "ubase_eq.h" +#include "ubase_ubus.h" + +#define UBASE_MOD_VERSION "1.0" + +#define ubase_dbg(_udev, fmt, ...) do { \ + if (ubase_dbg_default()) \ + dev_info(_udev->dev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__); \ + } while (0) + +#define ubase_err(_udev, fmt, ...) \ + dev_err(_udev->dev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__) + +#define ubase_info(_udev, fmt, ...) \ + dev_info(_udev->dev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__) + +#define ubase_warn(_udev, fmt, ...) \ + dev_warn(_udev->dev, "(pid %d) " fmt, \ + current->pid, ##__VA_ARGS__) + +struct ubase_adev { + struct auxiliary_device adev; + struct ubase_dev *udev; + struct atomic_notifier_head comp_nh; + struct notifier_block comp_notifier; + int idx; + + struct mutex virt_lock; + void (*virt_handler)(struct auxiliary_device *adev, u16 bus_ue_id, + bool is_en); + struct mutex port_lock; + void (*port_handler)(struct auxiliary_device *adev, bool link_up); + struct mutex reset_lock; + void (*reset_handler)(struct auxiliary_device *adev, + enum ubase_reset_stage stage); + struct mutex activate_lock; + void (*activate_handler)(struct auxiliary_device *adev, bool activate); +}; + +struct ubase_priv { + struct ubase_adev *uadev[UBASE_DRV_MAX]; + struct mutex uadev_lock; /* protect uadev[] */ +}; + +struct ubase_dev_caps { + struct ubase_adev_caps udma_caps; + struct ubase_adev_caps unic_caps; + struct ubase_caps dev_caps; + struct ubase_ue_caps ue_caps; +}; + +struct ubase_mbox_cmd { + struct dma_pool *pool; + struct semaphore sem; + struct ubase_mbx_event_context ctx; +}; + +struct ubase_destroy_res_cmd { + u8 destroy_done; + u8 rsv[23]; +}; + +struct ubase_dma_buf { + void *addr; + dma_addr_t dma_addr; + size_t size; +}; + +struct ubase_ctx_page { + dma_addr_t iova; + u32 npage; + refcount_t refcount; +}; + +struct ubase_ta_layer_ctx { + struct ubase_dma_buf extdb_buf; + struct ubase_dma_buf timer_buf; +}; + +struct ubase_tp_layer_ctx { + spinlock_t tpg_lock; + struct ubase_tpg *tpg; +}; + +struct ubase_reset_stat { + u32 reset_done_cnt; + u32 hw_reset_done_cnt; + u32 elr_reset_cnt; + u32 reset_fail_cnt; + u32 reset_retry_cnt; + u32 port_reset_cnt; + u32 himac_reset_cnt; +}; + +enum ubase_dev_state_bit { + UBASE_STATE_INITED_B, + UBASE_STATE_DISABLED_B, + UBASE_STATE_RST_HANDLING_B, + UBASE_STATE_IRQ_INVALID_B, + UBASE_STATE_PORT_RESETTING_B, + UBASE_STATE_HIMAC_RESETTING_B, + UBASE_STATE_CTX_READY_B, + UBASE_STATE_PREALLOC_OK_B, +}; + +struct ubase_crq_event_nbs { + struct list_head list; + struct ubase_crq_event_nb nb; +}; + +struct ubase_crq_table { + struct mutex lock; + unsigned long last_crq_scheduled; + struct ubase_crq_event_nbs nbs; +}; + +#define UBASE_CTRLQ_DATA_NUM 5 + +#pragma pack(push, 1) +struct ubase_ctrlq_base_block { + u8 service_ver; + u8 service_type; + u8 bb_num; + u8 opcode; + u8 ret; + __le16 seq; + u8 mbx_ue_id; + __le16 bus_ue_id; + __le16 rsv; + __le32 data[UBASE_CTRLQ_DATA_NUM]; +}; +#pragma pack(pop) + +struct ubase_ctrlq_ring { + u16 ci; + u16 pi; + u16 depth; + u32 tx_timeout; + void __iomem *base_addr; + spinlock_t lock; +}; + +struct ubase_ctrlq_msg_ctx { + u8 valid : 1; + u8 is_sync : 1; + u8 rsv0 : 6; + u8 mbx_ue_id; + u8 result; + u8 rsv1; + u16 ue_seq; + u16 bus_ue_id; + u16 out_size; + void *out; + unsigned long dead_jiffies; + struct completion done; +}; + +struct ubase_ctrlq_crq_event_nbs { + struct list_head list; + struct ubase_ctrlq_event_nb crq_nb; +}; + +struct ubase_ctrlq_crq_table { + struct mutex lock; + unsigned long last_crq_scheduled; + struct ubase_ctrlq_crq_event_nbs crq_nbs; +}; + +struct ubase_ctrlq { + u16 csq_next_seq; + unsigned long state; + atomic_t req_cnt; + struct ubase_ctrlq_ring csq; + struct ubase_ctrlq_ring crq; + struct ubase_ctrlq_msg_ctx *msg_queue; + struct ubase_ctrlq_crq_table crq_table; +}; + +#define UBASE_ACT_STAT_MAX_NUM 10U +struct ubase_activate_dev_stats { + u64 act_cnt; + u64 deact_cnt; + struct { + bool activate; + int result; + time64_t time; + } stats[UBASE_ACT_STAT_MAX_NUM]; + struct mutex lock; +}; + +struct ubase_stats { + struct mutex stats_lock; + struct ubase_eth_mac_stats eth_stats; + struct ubase_activate_dev_stats activate_record; +}; + +struct ubase_act_info { + u16 wait_msn; + int result; + struct completion activate_done; +}; + +struct ubase_act_ctx { + u16 msn; + struct ubase_act_info self; + struct ubase_act_info other; + struct mutex lock; +}; + +struct ubase_arq_msg { + u16 opcode; + void *data; + u32 data_len; +}; + +#define MAX_ARQ_MSG_NUM 128 +struct ubase_arq_msg_ring { + u8 pi; + u8 ci; + atomic_t count; + struct ubase_arq_msg msg[MAX_ARQ_MSG_NUM]; +}; + +struct ubase_pmem_ctx { + u16 page_cnt; + struct page **pgs; + struct scatterlist *sg; + dma_addr_t dma_addr; +}; + +struct ubase_prealloc_mem_info { + struct ubase_pmem_ctx comm; + struct ubase_pmem_ctx udma; +}; + +struct ubase_dev { + struct device *dev; + int dev_id; + struct ubase_priv priv; + struct ubase_hw hw; + + struct ubase_dev_caps caps; + struct ubase_adev_qos qos; + struct ubase_dbgfs dbgfs; + struct ubase_ctx_buf ctx_buf; + struct ubase_ta_layer_ctx ta_ctx; + struct ubase_tp_layer_ctx tp_ctx; + u32 cap_bits[UBASE_CAP_LEN]; + struct ubase_irq_table irq_table; + struct ubase_mbox_cmd mb_cmd; + struct workqueue_struct *ubase_wq; + struct workqueue_struct *ubase_async_wq; + struct workqueue_struct *ubase_reset_wq; + struct workqueue_struct *ubase_period_wq; + struct workqueue_struct *ubase_arq_wq; + unsigned long serv_proc_cnt; + struct ubase_delay_work service_task; + struct ubase_delay_work reset_service_task; + struct ubase_delay_work period_service_task; + struct ubase_delay_work arq_service_task; + struct ubase_crq_table crq_table; + struct ubase_ctrlq ctrlq; + unsigned long state_bits; + struct list_head ue_list; + struct mutex ue_list_lock; + + struct ubase_reset_stat reset_stat; + enum ubase_reset_type reset_type; + unsigned long last_reset_scheduled; + enum ubase_reset_stage reset_stage; + struct ubase_stats stats; + struct ubase_act_ctx act_ctx; + struct ubase_arq_msg_ring arq; + struct ubase_prealloc_mem_info pmem_info; +}; + +#define UBASE_ERR_MSG_LEN 128 +struct ubase_init_function { + char err_msg[UBASE_ERR_MSG_LEN]; + u32 support_devs; + u8 need_reset; + int (*init_func)(struct ubase_dev *udev); + void (*uninit_func)(struct ubase_dev *udev); +}; + +bool ubase_dbg_default(void); +bool ubase_dev_urma_supported(struct ubase_dev *udev); +bool ubase_dev_unic_supported(struct ubase_dev *udev); +bool ubase_dev_cdma_supported(struct ubase_dev *udev); +bool ubase_dev_pmu_supported(struct ubase_dev *udev); +bool ubase_dev_fwctl_supported(struct ubase_dev *udev); + +static inline +struct ubase_dev *__ubase_get_udev_by_adev(struct auxiliary_device *adev) +{ + struct ubase_adev *uadev = container_of(adev, struct ubase_adev, adev); + return uadev->udev; +} + +static inline bool ubase_dev_uvb_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_UVB_B); +} + +static inline bool ubase_ip_over_urma_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_IP_OVER_URMA_B); +} + +static inline bool ubase_ip_over_urma_utp_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_IP_OVER_URMA_UTP_B); +} + +static inline +struct ubase_dev *ubase_get_udev_by_adev(struct auxiliary_device *adev) +{ + if (!adev) + return NULL; + + return __ubase_get_udev_by_adev(adev); +} + +static inline bool ubase_dev_udma_supported(struct ubase_dev *udev) +{ + return ubase_dev_urma_supported(udev) && + !ubase_get_cap_bit(udev, UBASE_SUPPORT_UDMA_DISABLE_B); +} + +static inline bool ubase_dev_ubl_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_UBL_B); +} + +static inline bool ubase_dev_ta_extdb_buf_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_TA_EXTDB_BUF_B); +} + +static inline bool ubase_dev_ta_timer_buf_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_TA_TIMER_BUF_B); +} + +static inline bool ubase_dev_err_handle_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_ERR_HANDLE_B); +} + +static inline bool ubase_dev_ctrlq_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_CTRLQ_B); +} + +static inline bool ubase_dev_eth_mac_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_ETH_MAC_B); +} + +static inline bool ubase_dev_mac_stats_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_MAC_STATS_B); +} + +static inline bool __ubase_dev_prealloc_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_PRE_ALLOC_B); +} + +static inline bool ubase_activate_proxy_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_ACTIVATE_PROXY_B); +} + +static inline bool ubase_utp_supported(struct ubase_dev *udev) +{ + return ubase_get_cap_bit(udev, UBASE_SUPPORT_UTP_B); +} + +static inline bool ubase_dev_prealloc_supported(struct ubase_dev *udev) +{ + return __ubase_dev_prealloc_supported(udev) && + PAGE_SIZE != UBASE_PMEM_PAGE_SIZE; +} + +static inline u32 ubase_jfs_num(struct ubase_dev *udev) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + struct ubase_adev_caps *udma_caps = &udev->caps.udma_caps; + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + + return unic_caps->jfs.max_cnt + udma_caps->jfs.max_cnt + + dev_caps->public_jetty_cnt + dev_caps->rsvd_jetty_cnt; +} + +static inline u32 ubase_jfs_ctx_align_size(struct ubase_dev *udev) +{ + return ALIGN(ubase_jfs_num(udev) * UBASE_JFS_CTX_SIZE, PAGE_SIZE); +} + +static inline u32 ubase_ta_timer_align_size(struct ubase_dev *udev) +{ + return ALIGN(udev->ta_ctx.timer_buf.size, PAGE_SIZE); +} + +static inline bool ubase_mbx_ue_id_is_valid(u16 mbx_ue_id, + struct ubase_dev *udev) +{ + + if (!mbx_ue_id || (mbx_ue_id > udev->caps.dev_caps.ue_num - 1)) + return false; + + return true; +} + +int ubase_adev_idx_alloc(void); +void ubase_adev_idx_free(int id); + +void ubase_port_down(struct ubase_dev *udev); +void ubase_port_up(struct ubase_dev *udev); + +int ubase_dev_init(struct ubase_dev *udev); +void ubase_dev_uninit(struct ubase_dev *udev); + +int ubase_dev_reset_init(struct ubase_dev *udev); +void ubase_dev_reset_uninit(struct ubase_dev *udev); + +void ubase_suspend_aux_devices(struct ubase_dev *udev); +void ubase_resume_aux_devices(struct ubase_dev *udev); + +void ubase_virt_handler(struct ubase_dev *udev, u16 bus_ue_id, bool is_en); + +int ubase_activate_handler(struct ubase_dev *udev, u32 bus_ue_id); +int ubase_deactivate_handler(struct ubase_dev *udev, u32 bus_ue_id); + +#endif diff --git a/drivers/ub/ubase/ubase_eq.c b/drivers/ub/ubase/ubase_eq.c new file mode 100644 index 0000000000000000000000000000000000000000..754f62011420d467d5021593e095214f17788e97 --- /dev/null +++ b/drivers/ub/ubase/ubase_eq.c @@ -0,0 +1,1164 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include + +#include "ubase_cmd.h" +#include "ubase_dev.h" +#include "ubase_mailbox.h" +#include "ubase_reset.h" +#include "ubase_tp.h" +#include "ubase_trace.h" +#include "ubase_eq.h" + +enum ubase_eq_type { + UBASE_EQ_TYPE_AEQ, + UBASE_EQ_TYPE_CEQ, +}; + +static struct ubase_ceqe *ubase_next_ceqe(struct ubase_ceq *ceq) +{ + struct ubase_eq *eq = &ceq->eq; + struct ubase_ceqe *ceqe; + + ceqe = (struct ubase_ceqe *)(ceq->eq.addr.addr + + (eq->cons_index & (eq->entries_num - 1)) * + eq->eqe_size); + + return !!u32_get_bits(ceqe->comp, UBASE_CEQ_CEQE_OWNER_BIT) ^ + !!(eq->cons_index & eq->entries_num) ? ceqe : NULL; +} + +static void ubase_comp_handler(struct ubase_dev *udev, u32 jfcn) +{ + struct ubase_res_caps *unic_jfc_caps = &udev->caps.unic_caps.jfc; + u32 jfcn_end = unic_jfc_caps->start_idx + unic_jfc_caps->max_cnt; + u32 jfcn_begin = unic_jfc_caps->start_idx; + struct ubase_adev *uadev; + u8 idx; + + /* CDMA exists independently, UNIC/UDMA are distinguished by jfcn. */ + if (udev->priv.uadev[UBASE_DRV_CDMA]) + idx = UBASE_DRV_CDMA; + else if (jfcn_begin <= jfcn && jfcn < jfcn_end) + idx = UBASE_DRV_UNIC; + else + idx = UBASE_DRV_UDMA; + + uadev = udev->priv.uadev[idx]; + if (unlikely(!uadev)) + return; + + atomic_notifier_call_chain(&uadev->comp_nh, jfcn, (void *)&uadev->adev); +} + +static void ubase_update_eq_db(struct ubase_eq *eq, enum ubase_eq_type eq_type) +{ + struct ubase_eq_db eq_db = {0}; + + eq_db.eqn = eq->eqn; + eq_db.ci = eq->cons_index; + + if (eq_type == UBASE_EQ_TYPE_CEQ) + eq_db.type = UBASE_EQ_DB_CMD_CEQ; + + writeq(*(__le64 *)&eq_db, eq->db_reg); +} + +static irqreturn_t ubase_ceq_int_handler(int irq, void *data) +{ +#define UBASE_CEQ_POLLING_BUDGET 128 + + struct ubase_ceq *ceq = (struct ubase_ceq *)data; + struct ubase_ceqe *ceqe = ubase_next_ceqe(ceq); + struct ubase_dev *udev = ceq->udev; + bool ceqe_found = false; + u8 cnt = 0; + u32 jfcn; + + while (cnt++ < UBASE_CEQ_POLLING_BUDGET && ceqe) { + /* Make sure we read CEQ entry after we have checked the + * ownership bit + */ + dma_rmb(); + + jfcn = u32_get_bits(ceqe->comp, UBASE_CEQE_COMP_CQN_M); + + trace_ubase_ceqe(udev->dev, jfcn, &ceq->eq); + + ubase_comp_handler(udev, jfcn); + + ++ceq->eq.cons_index; + ceqe_found = true; + ceqe = ubase_next_ceqe(ceq); + } + + if (ceqe_found) + ubase_update_eq_db(&ceq->eq, UBASE_EQ_TYPE_CEQ); + + return IRQ_RETVAL(ceqe_found); +} + +static struct ubase_aeqe *ubase_next_aeqe(struct ubase_dev *udev, + struct ubase_aeq *aeq) +{ + struct ubase_eq *eq = &aeq->eq; + struct ubase_aeqe *aeqe; + + aeqe = (struct ubase_aeqe *)(eq->addr.addr + + (eq->cons_index & (eq->entries_num - 1)) * + eq->eqe_size); + + return aeqe->owner ^ !!(eq->cons_index & eq->entries_num) ? aeqe : NULL; +} + +void ubase_enable_misc_vector(struct ubase_dev *udev, bool enable) +{ + ubase_write_dev(&udev->hw, UBASE_MISC_VECTOR_REG_OFFSET, + enable ? 0 : 1); +} + +static void ubase_save_ras_type(struct ubase_dev *udev, u32 reg) +{ + if (ubase_dev_ubl_supported(udev)) { + set_bit(UBASE_STATE_PORT_RESETTING_B, &udev->state_bits); + return; + } + + if (reg & BIT(UBASE_SW_HANDSHAKE_0_HIMAC_RESET_B)) + set_bit(UBASE_STATE_HIMAC_RESETTING_B, &udev->state_bits); + else if (reg & BIT(UBASE_SW_HANDSHAKE_0_PORT_RESET_B)) + set_bit(UBASE_STATE_PORT_RESETTING_B, &udev->state_bits); +} + +static unsigned long ubase_check_event_cause(struct ubase_dev *udev) +{ + unsigned long event_cause = 0; + u32 sw_handshake_0_reg; + u32 ctrlq_src_reg; + u32 cmdq_src_reg; + + cmdq_src_reg = ubase_read_dev(&udev->hw, UBASE_VECTOR0_CMDQ_SRC_REG); + if (cmdq_src_reg & BIT(UBASE_VECTOR0_RX_CMDQ_INT_B)) + event_cause |= BIT(UBASE_ASYNC_EVENT_CRQ_B); + + sw_handshake_0_reg = ubase_read_dev(&udev->hw, + UBASE_SW_HANDSHAKE_0_REG); + if (sw_handshake_0_reg & BIT(UBASE_SW_HANDSHAKE_0_RAS_B)) { + ubase_save_ras_type(udev, sw_handshake_0_reg); + event_cause |= BIT(UBASE_ASYNC_EVENT_RAS_B); + } + + ctrlq_src_reg = ubase_read_dev(&udev->hw, UBASE_VECTOR0_CTRLQ_SRC_REG); + if (ctrlq_src_reg & BIT(UBASE_VECTOR0_RX_CTRLQ_INT_B)) + event_cause |= BIT(UBASE_ASYNC_EVENT_CTRLQ_B); + + return event_cause; +} + +static void ubase_clear_event_cause(struct ubase_dev *udev, + unsigned long event_cause) +{ + u32 sw_handshake_0_reg; + + if (test_bit(UBASE_ASYNC_EVENT_CRQ_B, &event_cause)) + ubase_write_dev(&udev->hw, UBASE_VECTOR0_CMDQ_SRC_REG, + BIT(UBASE_VECTOR0_RX_CMDQ_INT_B)); + + if (test_bit(UBASE_ASYNC_EVENT_RAS_B, &event_cause)) { + sw_handshake_0_reg = ubase_read_dev(&udev->hw, + UBASE_SW_HANDSHAKE_0_REG); + sw_handshake_0_reg &= ~(BIT(UBASE_SW_HANDSHAKE_0_RAS_B) | + BIT(UBASE_SW_HANDSHAKE_0_PORT_RESET_B) | + BIT(UBASE_SW_HANDSHAKE_0_HIMAC_RESET_B)); + ubase_write_dev(&udev->hw, UBASE_SW_HANDSHAKE_0_REG, + sw_handshake_0_reg); + } + + if (test_bit(UBASE_ASYNC_EVENT_CTRLQ_B, &event_cause)) + ubase_write_dev(&udev->hw, UBASE_VECTOR0_CTRLQ_SRC_REG, + BIT(UBASE_VECTOR0_RX_CTRLQ_INT_B)); +} + +static void ubase_clear_all_event_cause(struct ubase_dev *udev) +{ + ubase_clear_event_cause(udev, + BIT(UBASE_ASYNC_EVENT_CRQ_B) | + BIT(UBASE_ASYNC_EVENT_RAS_B) | + BIT(UBASE_ASYNC_EVENT_CTRLQ_B)); +} + +static void ubase_crq_task_schedule(struct ubase_dev *udev) +{ + if (!test_and_set_bit(UBASE_STATE_CRQ_SERVICE_SCHED, + &udev->service_task.state)) { + udev->crq_table.last_crq_scheduled = jiffies; + mod_delayed_work(udev->ubase_wq, + &udev->service_task.service_task, 0); + } +} + +static void ubase_errhandle_task_schedule(struct ubase_dev *udev) +{ + if (!test_and_set_bit(UBASE_SERVICE_STATE_ERR_SCHED, + &udev->service_task.state)) + mod_delayed_work(udev->ubase_wq, + &udev->service_task.service_task, 0); +} + +static void ubase_ctrlq_task_schedule(struct ubase_dev *udev) +{ + if (!test_and_set_bit(UBASE_STATE_CTRLQ_SERVICE_SCHED, + &udev->service_task.state)) { + udev->ctrlq.crq_table.last_crq_scheduled = jiffies; + mod_delayed_work(udev->ubase_wq, + &udev->service_task.service_task, 0); + } +} + +static int ubase_reg_event_handler(struct ubase_dev *udev) +{ + unsigned long event_cause; + + ubase_enable_misc_vector(udev, false); + + event_cause = ubase_check_event_cause(udev); + if (test_bit(UBASE_ASYNC_EVENT_CRQ_B, &event_cause)) + ubase_crq_task_schedule(udev); + + if (test_bit(UBASE_ASYNC_EVENT_RAS_B, &event_cause)) + ubase_errhandle_task_schedule(udev); + + if (test_bit(UBASE_ASYNC_EVENT_CTRLQ_B, &event_cause)) + ubase_ctrlq_task_schedule(udev); + + ubase_clear_event_cause(udev, event_cause); + ubase_enable_misc_vector(udev, true); + + return event_cause ? IRQ_HANDLED : IRQ_NONE; +} + +static bool ubase_is_udma_queue_id(u32 queue_id, struct ubase_res_caps *caps) +{ + u32 end = caps->start_idx + caps->max_cnt; + u32 begin = caps->start_idx; + + return begin <= queue_id && queue_id < end; +} + +static inline bool ubase_is_public_or_rsvd_jetty(u32 queue_id, + struct ubase_caps *caps) +{ + return queue_id < caps->public_jetty_cnt + caps->rsvd_jetty_cnt; +} + +static bool ubase_is_udma_ras_event(struct ubase_dev *udev, u32 queue_id, + u8 sub_type) +{ + switch (sub_type) { + case UBASE_SUBEVENT_TYPE_JFS_CHECK_ERROR: + return ubase_is_public_or_rsvd_jetty(queue_id, + &udev->caps.dev_caps) || + ubase_is_udma_queue_id(queue_id, + &udev->caps.udma_caps.jfs); + case UBASE_SUBEVENT_TYPE_JFR_CHECK_ERROR: + return ubase_is_udma_queue_id(queue_id, + &udev->caps.udma_caps.jfr); + case UBASE_SUBEVENT_TYPE_JFC_CHECK_ERROR: + return ubase_is_udma_queue_id(queue_id, + &udev->caps.udma_caps.jfc); + case UBASE_SUBEVENT_TYPE_JETTY_GROUP_CHECK_ERROR: + return true; + default: + break; + } + + return false; +} + +static bool ubase_is_udma_tp_event(struct ubase_dev *udev, u32 tpn) +{ + struct ubase_tpg *tpg = udev->tp_ctx.tpg; + u32 i; + + spin_lock(&udev->tp_ctx.tpg_lock); + if (!tpg) { + ubase_warn(udev, "unexpected tp event, tpn = %u.\n", tpn); + spin_unlock(&udev->tp_ctx.tpg_lock); + return true; + } + + for (i = 0; i < udev->caps.unic_caps.tpg.max_cnt; i++) { + if (tpn >= tpg[i].start_tpn && + tpn < tpg[i].start_tpn + tpg[i].tp_cnt) { + spin_unlock(&udev->tp_ctx.tpg_lock); + return false; + } + } + spin_unlock(&udev->tp_ctx.tpg_lock); + + return true; +} + +static bool ubase_is_udma_event(struct ubase_dev *udev, u8 event_type, + u8 sub_type, struct ubase_aeqe *aeqe) +{ + u32 queue_id = aeqe->event.queue_event.num; + + switch (event_type) { + case UBASE_EVENT_TYPE_CHECK_TOKEN: + return true; + case UBASE_EVENT_TYPE_TP_FLUSH_DONE: + case UBASE_EVENT_TYPE_TP_LEVEL_ERROR: + return ubase_is_udma_tp_event(udev, queue_id); + case UBASE_EVENT_TYPE_JFR_LIMIT_REACHED: + return ubase_is_udma_queue_id(queue_id, + &udev->caps.udma_caps.jfr); + case UBASE_EVENT_TYPE_JETTY_LEVEL_ERROR: + return ubase_is_udma_ras_event(udev, queue_id, sub_type); + default: + break; + } + + return false; +} + +static bool ubase_is_comm_event(struct ubase_dev *udev, struct ubase_aeqe *aeqe) +{ + switch (aeqe->event_type) { + case UBASE_EVENT_TYPE_MB: + return true; + default: + return false; + } + + return false; +} + +static void ubase_aeq_event_handler(struct ubase_dev *udev, + struct ubase_aeqe *aeqe) +{ + struct ubase_aeq_notify_info info; + u8 event_type = aeqe->event_type; + u8 sub_type = aeqe->sub_type; + u8 idx; + + if (event_type >= UBASE_EVENT_TYPE_MAX) { + ubase_err(udev, "event type wrong, event_type = %u.\n", + event_type); + return; + } + + info.event_type = event_type; + info.sub_type = sub_type; + info.aeqe = aeqe; + + if (ubase_is_comm_event(udev, aeqe)) + idx = UBASE_DRV_UNIC; + else if (ubase_dev_cdma_supported(udev)) + idx = UBASE_DRV_CDMA; + else if (ubase_is_udma_event(udev, event_type, sub_type, aeqe)) + idx = UBASE_DRV_UDMA; + else + idx = UBASE_DRV_UNIC; + + ubase_dbg(udev, "ubase do async work, idx = %u, event_type = %u.\n", + idx, event_type); + + blocking_notifier_call_chain(&udev->irq_table.nh[idx][event_type], + event_type, (void *)&info); +} + +static void ubase_async_service_task(struct work_struct *work) +{ + struct ubase_aeq_work *aeq_work = + container_of(work, struct ubase_aeq_work, work); + struct ubase_aeqe *aeqe = &aeq_work->aeqe; + struct ubase_dev *udev = aeq_work->udev; + + ubase_aeq_event_handler(udev, aeqe); + + kfree(aeq_work); +} + +static void ubase_init_aeq_work(struct ubase_dev *udev, struct ubase_aeqe *aeqe) +{ + struct ubase_aeq_work *aeq_work; + + aeq_work = kzalloc(sizeof(struct ubase_aeq_work), GFP_ATOMIC); + if (!aeq_work) { + dev_err_ratelimited(udev->dev, "failed to alloc aeq work.\n"); + return; + } + + aeq_work->udev = udev; + memcpy(&aeq_work->aeqe, aeqe, sizeof(struct ubase_aeqe)); + INIT_WORK(&aeq_work->work, ubase_async_service_task); + + queue_work(udev->ubase_async_wq, &aeq_work->work); +} + +static int ubase_async_event_handler(struct ubase_dev *udev) +{ + struct ubase_aeq *aeq = &udev->irq_table.aeq; + struct ubase_eq *eq = &aeq->eq; + struct ubase_aeqe *aeqe; + int ret = IRQ_NONE; + + aeqe = ubase_next_aeqe(udev, aeq); + while (aeqe) { + dma_rmb(); + + trace_ubase_aeqe(udev->dev, aeqe, eq); + + ubase_dbg(udev, + "event_type = 0x%x, sub_type = 0x%x, owner = %u, seq_num = %u, cons_index = %u.\n", + aeqe->event_type, aeqe->sub_type, aeqe->owner, + aeqe->event.cmd.seq_num, eq->cons_index); + + ret = IRQ_HANDLED; + + ubase_init_aeq_work(udev, aeqe); + + ++aeq->eq.cons_index; + aeqe = ubase_next_aeqe(udev, aeq); + + ubase_update_eq_db(&aeq->eq, UBASE_EQ_TYPE_AEQ); + } + + return ret; +} + +static irqreturn_t ubase_misc_int_handler(int irq, void *data) +{ + struct ubase_dev *udev = (struct ubase_dev *)data; + + return IRQ_RETVAL(ubase_reg_event_handler(udev)); +} + +static irqreturn_t ubase_aeq_int_handler(int irq, void *data) +{ + struct ubase_dev *udev = (struct ubase_dev *)data; + + ubase_dbg(udev, "ubase enter aeq handler.\n"); + + return IRQ_RETVAL(ubase_async_event_handler(udev)); +} + +static void ubase_construct_eq_ctx(struct ubase_eq *eq, + struct ubase_eq_ctx *ctx, u32 tid) +{ + ctx->state = eq->state; + ctx->arm_st = eq->arm_st; + ctx->eqe_size = eq->eqe_size == UBASE_DEFAULT_EQE_SIZE ? 1 : 0; + ctx->pi = UBASE_EQ_INIT_PROD_IDX; + ctx->shift = ilog2(eq->entries_num) - UBASE_CTX_SHIFT_BASE; + ctx->eqe_coalesce_period = eq->eq_period; + ctx->ci = UBASE_EQ_INIT_CONS_IDX; + ctx->eqe_coalesce_cnt = eq->coalesce_cnt; + ctx->eqe_base_addr_l = eq->addr.dma_addr >> + UBASE_EQE_BA_L_OFFSET & UBASE_EQE_BA_L_VALID_BIT; + ctx->eqe_base_addr_h = eq->addr.dma_addr >> + UBASE_EQE_BA_H_OFFSET & UBASE_EQE_BA_H_VALID_BIT; + ctx->eqe_token_id = tid; + ctx->irq_num = eq->eqc_irqn; + ctx->pi_bypass = UBASE_EQ_INIT_PROD_IDX; + ctx->state2 = eq->state; +} + +static int ubase_fill_eq_attribute(struct ubase_dev *udev, struct ubase_eq *eq, + u32 eqn, struct ubase_irq *irq, + enum ubase_eq_type eq_type) +{ + struct ubase_eq_addr *eq_addr = &eq->addr; + + if (eq_type == UBASE_EQ_TYPE_AEQ) { + eq->eqe_size = udev->caps.dev_caps.aeqe_size; + eq->entries_num = udev->caps.dev_caps.aeqe_depth; + eq->eq_period = EQC_EQ_MAX_PERIOD_INDX; + eq->eqc_irqn = eqn + udev->caps.dev_caps.num_misc_vectors; + } else { + eq->eqe_size = udev->caps.dev_caps.ceqe_size; + eq->entries_num = udev->caps.dev_caps.ceqe_depth; + eq->eqc_irqn = eqn + udev->caps.dev_caps.num_misc_vectors + + udev->caps.dev_caps.num_aeq_vectors; + eq->eq_period = EQC_EQ_MAX_PERIOD_INDX; + } + + eq->cons_index = 0; + eq->db_reg = udev->hw.mem_base.addr; + eq->eqn = eqn; + eq->state = UBASE_EQ_STAT_VALID; + eq->arm_st = UBASE_EQ_ALWAYS_ARMED; + eq->coalesce_cnt = UBASE_EQ_COALESCE_0; + eq->irqn = irq->irqn; + + eq_addr->size = eq->entries_num * eq->eqe_size; + eq_addr->addr = dma_alloc_coherent(udev->dev, eq_addr->size, + &eq_addr->dma_addr, GFP_KERNEL); + if (!eq_addr->addr) { + ubase_err(udev, "failed to alloc eqe base addr.\n"); + return -ENOMEM; + } + + return 0; +} + +static int ubase_create_eq(struct ubase_dev *udev, struct ubase_eq *eq, u32 eqn, + struct ubase_irq *irq, enum ubase_eq_type eq_type) +{ + struct ubase_cmd_mailbox *mbx; + struct ubase_mbx_attr attr; + int mbx_cmd; + int ret; + + ret = ubase_fill_eq_attribute(udev, eq, eqn, irq, eq_type); + if (ret) { + ubase_err(udev, "failed to fill eq attribute.\n"); + return ret; + } + + mbx_cmd = eq_type == UBASE_EQ_TYPE_AEQ ? UBASE_MB_CREATE_AEQ_CONTEXT : + UBASE_MB_CREATE_CEQ_CONTEXT; + mbx = __ubase_alloc_cmd_mailbox(udev); + if (IS_ERR_OR_NULL(mbx)) { + ubase_err(udev, "failed to alloc mailbox for create EQC.\n"); + ret = -ENOMEM; + goto err_alloc_mailbox; + } + ubase_construct_eq_ctx(eq, (struct ubase_eq_ctx *)mbx->buf, + udev->caps.dev_caps.tid); + ubase_fill_mbx_attr(&attr, eq->eqn, mbx_cmd, 0); + ret = ubase_hw_upgrade_ctx_poll(udev, &attr, mbx); + if (ret) { + ubase_err(udev, "failed to create EQC, ret = %d.\n", ret); + goto err_upgrade_ctx; + } + + __ubase_free_cmd_mailbox(udev, mbx); + + return 0; + +err_upgrade_ctx: + __ubase_free_cmd_mailbox(udev, mbx); +err_alloc_mailbox: + dma_free_coherent(udev->dev, eq->addr.size, eq->addr.addr, + eq->addr.dma_addr); + eq->addr.addr = NULL; + + return ret; +} + +static int ubase_destroy_eq(struct ubase_dev *udev, struct ubase_eq *eq, + enum ubase_eq_type eq_type) +{ + struct ubase_cmd_mailbox *mbx; + struct ubase_mbx_attr attr; + int mbx_cmd; + int ret; + + mbx_cmd = eq_type == UBASE_EQ_TYPE_AEQ ? UBASE_MB_DESTROY_AEQ_CONTEXT : + UBASE_MB_DESTROY_CEQ_CONTEXT; + + mbx = __ubase_alloc_cmd_mailbox(udev); + if (IS_ERR_OR_NULL(mbx)) { + ubase_err(udev, + "failed to alloc mailbox for destroy EQC.\n"); + return -ENOMEM; + } + ubase_fill_mbx_attr(&attr, eq->eqn, mbx_cmd, 0); + ret = ubase_hw_upgrade_ctx_poll(udev, &attr, mbx); + if (ret) + ubase_err(udev, "failed to destroy EQC, ret = %d.\n", ret); + + __ubase_free_cmd_mailbox(udev, mbx); + dma_free_coherent(udev->dev, eq->addr.size, eq->addr.addr, + eq->addr.dma_addr); + eq->addr.addr = NULL; + + return ret; +} + +static int ubase_request_misc_irq(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + struct ubase_irq *irq; + int ret; + + if (ubase_ubus_irq_vector(udev->dev, 0) == -EOPNOTSUPP) + return 0; + + irq = irq_table->irqs[UBASE_MISC_IRQ_INDEX]; + snprintf(irq->name, UBASE_INT_NAME_LEN, "ubase%d-%s-%d", udev->dev_id, + "misc", 0); + ret = request_irq(irq->irqn, ubase_misc_int_handler, 0, irq->name, udev); + if (ret) { + ubase_err(udev, + "failed to request misc irq, ret = %d.\n", ret); + return ret; + } + + ubase_enable_misc_vector(udev, true); + + return ret; +} + +static int ubase_request_aeq_irq(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + struct ubase_aeq *aeq = &irq_table->aeq; + struct ubase_irq *irq; + int ret; + + irq = irq_table->irqs[UBASE_AEQ_IRQ_INDEX]; + snprintf(irq->name, UBASE_INT_NAME_LEN, "ubase%d-%s-%d", udev->dev_id, + "aeq", 0); + + ret = ubase_create_eq(udev, &aeq->eq, 0, irq, UBASE_EQ_TYPE_AEQ); + if (ret) { + ubase_err(udev, "failed to create aeq, ret = %d.\n", ret); + return ret; + } + + if (ubase_ubus_irq_vector(udev->dev, 0) == -EOPNOTSUPP) + return 0; + + ret = request_irq(irq->irqn, ubase_aeq_int_handler, 0, irq->name, udev); + if (ret) { + ubase_err(udev, + "failed to request aeq irq, ret = %d.\n", ret); + + if (ubase_destroy_eq(udev, &irq_table->aeq.eq, UBASE_EQ_TYPE_AEQ)) + ubase_err(udev, "failed to destroy aeq.\n"); + return ret; + } + + return 0; +} + +static void ubase_free_misc_irq(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + struct ubase_irq *irq; + + if (!irq_table->irqs) + return; + + ubase_enable_misc_vector(udev, false); + + irq = irq_table->irqs[UBASE_MISC_IRQ_INDEX]; + if (ubase_ubus_irq_vector(udev->dev, 0) != -EOPNOTSUPP) + free_irq(irq->irqn, udev); +} + +static void ubase_free_ceq_irqs(struct ubase_dev *udev) +{ + struct ubase_ceqs *ceqs = &udev->irq_table.ceqs; + u32 i; + + for (i = 0; i < ceqs->num; i++) { + if (ubase_ubus_irq_vector(udev->dev, 0) != -EOPNOTSUPP) + free_irq(ceqs->ceq[i].eq.irqn, &ceqs->ceq[i]); + } +} + +static void ubase_free_aeq_irq(struct ubase_dev *udev) +{ + struct ubase_aeq *aeq = &udev->irq_table.aeq; + + if (ubase_ubus_irq_vector(udev->dev, 0) != -EOPNOTSUPP) + free_irq(aeq->eq.irqn, udev); +} + +static void ubase_destroy_ceqs(struct ubase_dev *udev) +{ + struct ubase_ceqs *ceqs = &udev->irq_table.ceqs; + u32 i; + + if (!ceqs->ceq) + return; + + for (i = 0; i < ceqs->num; i++) { + if (ubase_destroy_eq(udev, &ceqs->ceq[i].eq, UBASE_EQ_TYPE_CEQ)) + ubase_err(udev, "failed to destroy ceq[%u].\n", i); + } + mutex_lock(&udev->irq_table.ceq_lock); + devm_kfree(udev->dev, ceqs->ceq); + ceqs->ceq = NULL; + mutex_unlock(&udev->irq_table.ceq_lock); +} + +static void ubase_destroy_aeq(struct ubase_dev *udev) +{ + struct ubase_aeq *aeq = &udev->irq_table.aeq; + + if (!aeq->eq.addr.addr) + return; + + if (ubase_destroy_eq(udev, &aeq->eq, UBASE_EQ_TYPE_AEQ)) + ubase_err(udev, "failed to destroy aeq.\n"); +} + +static int ubase_request_ceq_irq(struct ubase_dev *udev, struct ubase_ceq *ceq, + u32 index) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + struct ubase_irq *irq; + int ret; + + irq = irq_table->irqs[index + UBASE_CEQ_IRQ_INDEX]; + snprintf(irq->name, UBASE_INT_NAME_LEN, "ubase%d-%s-%u", udev->dev_id, + "ceq", index); + ceq->udev = udev; + ret = ubase_create_eq(udev, &ceq->eq, index, irq, UBASE_EQ_TYPE_CEQ); + if (ret) { + ubase_err(udev, + "failed to create ceq[%u], ret = %d.\n", index, ret); + return ret; + } + + if (ubase_ubus_irq_vector(udev->dev, 0) == -EOPNOTSUPP) + return 0; + + irq_set_status_flags(irq->irqn, IRQ_NOAUTOEN); + ret = request_irq(irq->irqn, ubase_ceq_int_handler, 0, irq->name, ceq); + if (ret) { + ubase_err(udev, "failed to request ceq[%u], ret = %d.\n", + index, ret); + if (ubase_destroy_eq(udev, &ceq->eq, UBASE_EQ_TYPE_CEQ)) + ubase_err(udev, "failed to destroy ceq.\n"); + } + + return ret; +} + +static int ubase_request_ceq_irqs(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + struct ubase_ceqs *ceqs = &irq_table->ceqs; + u32 ceq_irq_num, i; + int ret; + + mutex_lock(&udev->irq_table.ceq_lock); + ceq_irq_num = udev->caps.dev_caps.num_ceq_vectors; + ceqs->ceq = devm_kcalloc(udev->dev, ceq_irq_num, + sizeof(struct ubase_ceq), GFP_KERNEL); + if (!ceqs->ceq) { + mutex_unlock(&udev->irq_table.ceq_lock); + return -ENOMEM; + } + mutex_unlock(&udev->irq_table.ceq_lock); + + ceqs->num = ceq_irq_num; + + for (i = 0; i < ceq_irq_num; i++) { + ret = ubase_request_ceq_irq(udev, &ceqs->ceq[i], i); + if (ret) { + ubase_err(udev, + "failed to request ceq[%u] irq, ret = %d.\n", + i, ret); + ceqs->num = i; + goto err_alloc_ceq; + } + } + + return 0; + +err_alloc_ceq: + ubase_free_ceq_irqs(udev); + ubase_destroy_ceqs(udev); + + return ret; +} + +static int ubase_irq_init(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + u32 irqs_num = irq_table->irqs_num; + struct ubase_irq **irqs; + int ret; + u32 i; + + irqs = kcalloc(irqs_num, sizeof(struct ubase_irq *), GFP_KERNEL); + if (!irqs) { + ubase_err(udev, "failed to alloc irqs.\n"); + return -ENOMEM; + } + + for (i = 0; i < irqs_num; i++) { + irqs[i] = kzalloc(sizeof(struct ubase_irq), GFP_KERNEL); + if (!irqs[i]) { + ubase_err(udev, "failed to alloc ubase irq[%u].\n", i); + ret = -ENOMEM; + goto err_alloc_ubase_irq; + } + + if (ubase_ubus_irq_vector(udev->dev, 0) == -EOPNOTSUPP) + continue; + + irqs[i]->irqn = ubase_ubus_irq_vector(udev->dev, i); + if (irqs[i]->irqn < 0) { + ubase_err(udev, + "failed to get irq[%u] num, err irq num = %d.\n", + i, irqs[i]->irqn); + ret = irqs[i]->irqn; + kfree(irqs[i]); + goto err_alloc_ubase_irq; + } + } + irq_table->irqs = irqs; + + return 0; + +err_alloc_ubase_irq: + for (; i > 0; i--) + kfree(irqs[i - 1]); + kfree(irqs); + irq_table->irqs = NULL; + + return ret; +} + +static void ubase_irq_uninit(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + u32 i; + + if (!irq_table->irqs) + return; + + for (i = 0; i < irq_table->irqs_num; i++) + kfree(irq_table->irqs[i]); + kfree(irq_table->irqs); + irq_table->irqs = NULL; +} + +static int ubase_request_irq(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_request_misc_irq(udev); + if (ret) { + ubase_err(udev, + "failed to request ubase misc irq, ret = %d.\n", ret); + goto err_misc_init; + } + + ret = ubase_request_aeq_irq(udev); + if (ret) { + ubase_err(udev, + "failed to request ubase aeq irq, ret = %d.\n", ret); + goto err_aeq_init; + } + + ret = ubase_request_ceq_irqs(udev); + if (ret) { + ubase_err(udev, + "failed to request ubase ceq irqs, ret = %d.\n", ret); + goto err_ceq_init; + } + + return 0; + +err_ceq_init: + ubase_free_aeq_irq(udev); + ubase_destroy_aeq(udev); +err_aeq_init: + ubase_free_misc_irq(udev); +err_misc_init: + return ret; +} + +static void ubase_free_irq(struct ubase_dev *udev) +{ + ubase_free_ceq_irqs(udev); + ubase_free_aeq_irq(udev); + ubase_free_misc_irq(udev); +} + +int ubase_irq_table_init(struct ubase_dev *udev) +{ + struct ubase_irq_table *irq_table = &udev->irq_table; + int i, j, ret; + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + for (i = 0; i < UBASE_DRV_MAX; i++) { + for (j = 0; j < UBASE_EVENT_TYPE_MAX; j++) + BLOCKING_INIT_NOTIFIER_HEAD(&irq_table->nh[i][j]); + } + mutex_init(&udev->irq_table.ceq_lock); + } + + ret = ubase_ubus_irq_vectors_alloc(udev->dev); + if (ret) { + ubase_err(udev, "failed to alloc irq vectors, ret = %d.\n", + ret); + goto err_irq_res_init; + } + + ret = ubase_irq_init(udev); + if (ret) { + ubase_err(udev, "failed to init ubase irq, ret = %d.\n", ret); + goto err_irq_init; + } + + ubase_clear_all_event_cause(udev); + + ret = ubase_request_irq(udev); + if (ret) { + ubase_err(udev, "failed to request ubase irq, ret = %d.\n", + ret); + goto err_request_irq; + } + + clear_bit(UBASE_STATE_IRQ_INVALID_B, &udev->state_bits); + + return 0; + +err_request_irq: + ubase_irq_uninit(udev); +err_irq_init: + ubase_ubus_irq_vectors_free(udev->dev); +err_irq_res_init: + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + mutex_destroy(&udev->irq_table.ceq_lock); + + return ret; +} + +void ubase_irq_table_free(struct ubase_dev *udev) +{ + if (test_and_set_bit(UBASE_STATE_IRQ_INVALID_B, &udev->state_bits)) + return; + + ubase_free_irq(udev); + ubase_irq_uninit(udev); + ubase_ubus_irq_vectors_free(udev->dev); +} + +void ubase_irq_table_uninit(struct ubase_dev *udev) +{ + ubase_irq_table_free(udev); + ubase_destroy_ceqs(udev); + ubase_destroy_aeq(udev); + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + mutex_destroy(&udev->irq_table.ceq_lock); +} + +void ubase_disable_ce_irqs(struct ubase_dev *udev) +{ + struct ubase_ceqs *ceqs = &udev->irq_table.ceqs; + u32 i; + + if (test_bit(UBASE_STATE_IRQ_INVALID_B, &udev->state_bits)) + return; + + for (i = 0; i < ceqs->num; i++) + disable_irq(ceqs->ceq[i].eq.irqn); +} + +int ubase_enable_ce_irqs(struct ubase_dev *udev) +{ + struct ubase_ceqs *ceqs = &udev->irq_table.ceqs; + u32 i; + + if (test_bit(UBASE_STATE_IRQ_INVALID_B, &udev->state_bits)) + return 0; + + for (i = 0; i < ceqs->num; i++) + enable_irq(ceqs->ceq[i].eq.irqn); + + return 0; +} + +static int __ubase_event_register(struct ubase_dev *udev, + struct ubase_event_nb *cb) +{ + struct blocking_notifier_head *nh; + int ret; + + if (cb->drv_type >= UBASE_DRV_MAX) { + ubase_err(udev, "unsupported drv_type(%u).\n", cb->drv_type); + return -EINVAL; + } + + if (cb->event_type >= UBASE_EVENT_TYPE_MAX) { + ubase_err(udev, + "unsupported event type(%u).\n", cb->event_type); + return -EINVAL; + } + + nh = udev->irq_table.nh[cb->drv_type]; + ret = blocking_notifier_chain_register(&nh[cb->event_type], &cb->nb); + if (ret) + ubase_err(udev, + "failed to notifier chain register, type = %u, ret = %d.\n", + cb->event_type, ret); + + return ret; +} + +int ubase_event_register(struct auxiliary_device *adev, + struct ubase_event_nb *cb) +{ + if (!adev || !cb) + return -EINVAL; + + return __ubase_event_register(__ubase_get_udev_by_adev(adev), cb); +} +EXPORT_SYMBOL(ubase_event_register); + +static void __ubase_event_unregister(struct ubase_dev *udev, + struct ubase_event_nb *cb) +{ + struct blocking_notifier_head *nh; + int ret; + + if (cb->drv_type >= UBASE_DRV_MAX) { + ubase_err(udev, "unsupported drv_type(%u).\n", cb->drv_type); + return; + } + + if (cb->event_type >= UBASE_EVENT_TYPE_MAX) { + ubase_err(udev, + "unsupported event type(%u).\n", cb->event_type); + return; + } + + nh = udev->irq_table.nh[cb->drv_type]; + ret = blocking_notifier_chain_unregister(&nh[cb->event_type], &cb->nb); + if (ret) + ubase_err(udev, + "failed to unregister notifier chain, type = %u, ret = %d.\n", + cb->event_type, ret); +} + +void ubase_event_unregister(struct auxiliary_device *adev, + struct ubase_event_nb *cb) +{ + if (!adev || !cb) + return; + + __ubase_event_unregister(__ubase_get_udev_by_adev(adev), cb); +} +EXPORT_SYMBOL(ubase_event_unregister); + +int ubase_comp_register(struct auxiliary_device *adev, + int (*comp_handler)(struct notifier_block *nb, + unsigned long jfcn, void *data)) +{ + struct ubase_adev *uadev; + int ret; + + if (!adev || !comp_handler) + return -EINVAL; + + uadev = container_of(adev, struct ubase_adev, adev); + uadev->comp_notifier.notifier_call = comp_handler; + ret = atomic_notifier_chain_register(&uadev->comp_nh, + &uadev->comp_notifier); + if (ret) + ubase_err(uadev->udev, + "failed to register comp notifier chain, ret = %d.\n", + ret); + + return ret; +} +EXPORT_SYMBOL(ubase_comp_register); + +void ubase_comp_unregister(struct auxiliary_device *adev) +{ + struct ubase_adev *uadev; + int ret; + + if (!adev) + return; + + uadev = container_of(adev, struct ubase_adev, adev); + + ret = atomic_notifier_chain_unregister(&uadev->comp_nh, + &uadev->comp_notifier); + if (ret) + ubase_err(uadev->udev, + "failed to unregister comp notifier chain, ret = %d.\n", + ret); +} +EXPORT_SYMBOL(ubase_comp_unregister); + +static int ubase_ae_entity_level_error(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct ubase_event_nb *ev_nb = container_of(nb, + struct ubase_event_nb, nb); + struct ubase_aeq_notify_info *info = data; + struct ubase_dev *udev = ev_nb->back; + + ubase_err(udev, + "ubase async event for ue level error, event_type=0x%x, sub_type=0x%x.\n", + info->event_type, info->sub_type); + + __ubase_reset_event(udev, UBASE_UE_RESET); + + return 0; +} + +static void __ubase_unregister_ae_event(struct ubase_dev *udev, int num) +{ + int i; + + for (i = 0; i < num; i++) + __ubase_event_unregister(udev, &udev->irq_table.aeq.cb[i]); +} + +void ubase_unregister_ae_event(struct ubase_dev *udev) +{ + __ubase_unregister_ae_event(udev, UBASE_AE_LEVEL_NUM); +} + +int ubase_register_ae_event(struct ubase_dev *udev) +{ + struct ubase_event_nb ubase_ae_nbs[UBASE_AE_LEVEL_NUM] = { + { + UBASE_DRV_UNIC, + UBASE_EVENT_TYPE_MB, + { ubase_cmd_mbx_event_cb }, + udev + }, { + UBASE_DRV_UNIC, + UBASE_EVENT_TYPE_TP_FLUSH_DONE, + { ubase_ae_tp_flush_done }, + udev + }, { + UBASE_DRV_UNIC, + UBASE_EVENT_TYPE_TP_LEVEL_ERROR, + { ubase_ae_tp_level_error }, + udev + }, { + UBASE_DRV_UNIC, + UBASE_EVENT_TYPE_ENTITY_LEVEL_ERROR, + { ubase_ae_entity_level_error }, + udev + } + }; + struct ubase_aeq *aeq = &udev->irq_table.aeq; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ubase_ae_nbs); i++) { + aeq->cb[i] = ubase_ae_nbs[i]; + ret = __ubase_event_register(udev, &aeq->cb[i]); + if (ret) { + ubase_err(udev, + "failed to register asyn event[%u], ret = %d", + aeq->cb[i].event_type, ret); + __ubase_unregister_ae_event(udev, i); + return ret; + } + } + + return 0; +} diff --git a/drivers/ub/ubase/ubase_eq.h b/drivers/ub/ubase/ubase_eq.h new file mode 100644 index 0000000000000000000000000000000000000000..a4a15c9144f45e26714477ebeca3d410fbd79976 --- /dev/null +++ b/drivers/ub/ubase/ubase_eq.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_EQ_H__ +#define __UBASE_EQ_H__ + +#include + +#include "ubase.h" + +#define UBASE_MIN_IRQ_NUM 3 /* for misc aeq ceq */ + +#define UBASE_EQ_ALWAYS_ARMED 0x2 +#define UBASE_EQ_COALESCE_0 0 +#define UBASE_EQ_INIT_PROD_IDX 0 +#define UBASE_EQ_INIT_CONS_IDX 0 +#define UBASE_EQ_STAT_INVALID 0 +#define UBASE_EQ_STAT_VALID 1 + +#define UBASE_CTX_SHIFT_BASE 6 +#define UBASE_DEFAULT_EQE_SIZE 64 +#define UBASE_EQE_BA_L_OFFSET 12 +#define UBASE_EQE_BA_L_VALID_BIT GENMASK(19, 0) +#define UBASE_EQE_BA_H_OFFSET 32 +#define UBASE_EQE_BA_H_VALID_BIT GENMASK(31, 0) + +#define UBASE_CEQ_CEQE_OWNER_BIT BIT(31) +#define UBASE_CEQE_COMP_CQN_M GENMASK(19, 0) +#define UBASE_EQ_DB_CMD_CEQ 0x2 +#define EQC_EQ_MAX_PERIOD_INDX 4U + +#define UBASE_INT_NAME_LEN 32 + +#define UBASE_AE_LEVEL_NUM 4 + +/* Vector0 interrupt CMDQ event source register(RW) */ +#define UBASE_VECTOR0_CMDQ_SRC_REG 0x18004 +/* Vector0 interrupt CTRLQ event source register(RW) */ +#define UBASE_VECTOR0_CTRLQ_SRC_REG 0x18014 +/* Vector0 interrupt control register */ +#define UBASE_MISC_VECTOR_REG_OFFSET 0x18020 +/* Software handshake 0 register(RW) */ +#define UBASE_SW_HANDSHAKE_0_REG 0x18044 +/* CMDQ register bits for RX event */ +#define UBASE_VECTOR0_RX_CMDQ_INT_B 1 +/* CTRLQ register bits for RX event */ +#define UBASE_VECTOR0_RX_CTRLQ_INT_B 0 +/* Software handshake 0 register bit for ras report */ +#define UBASE_SW_HANDSHAKE_0_RAS_B 0 +#define UBASE_SW_HANDSHAKE_0_PORT_RESET_B 1 +#define UBASE_SW_HANDSHAKE_0_HIMAC_RESET_B 2 + +enum ubase_async_event_cause_bit { + UBASE_ASYNC_EVENT_CRQ_B, + UBASE_ASYNC_EVENT_RAS_B, + UBASE_ASYNC_EVENT_CTRLQ_B, +}; + +enum ubase_eqc_irqn { + UBASE_MISC_IRQ_INDEX, + UBASE_AEQ_IRQ_INDEX, + UBASE_CEQ_IRQ_INDEX, +}; + +struct ubase_irq { + char name[UBASE_INT_NAME_LEN]; + int irqn; +}; + +struct ubase_eq_db { + u8 eqn; + u8 rsv0; + __le16 type : 2; + __le16 rsv1 : 14; + + __le32 ci : 24; + __le32 rsv2 : 8; +}; + +struct ubase_ceqe { + u32 comp; + u32 rsv[15]; +}; + +struct ubase_eq_addr { + void *addr; + dma_addr_t dma_addr; + size_t size; +}; + +struct ubase_eq { + void __iomem *db_reg; + struct ubase_eq_addr addr; + u32 eqn; + u32 entries_num; + u32 state; + u32 arm_st; + u8 eqe_size; + u32 eq_period; + u32 coalesce_cnt; + int irqn; + u32 eqc_irqn; /* irqn for eqc */ + u32 cons_index; +}; + +struct ubase_eq_ctx { + /* DW0 */ + u32 state : 2; + u32 arm_st : 2; + u32 eqe_size : 1; + u32 rsv0 : 3; + u32 pi : 24; + + /* DW1 */ + u32 shift : 5; + u32 eqe_coalesce_period : 3; + u32 ci : 24; + + /* DW2 */ + u32 eqe_coalesce_cnt : 10; + u32 rsv1 : 2; + u32 eqe_base_addr_l : 20; + + /* DW3 */ + u32 eqe_base_addr_h; + + /* DW4 */ + u32 eqe_token_id : 20; + u32 rsv2 : 12; + + /* DW5 */ + u32 eqe_token_value; + + /* DW6 */ + u32 irq_num : 16; + u32 rsv3_1 : 16; + + /* DW7 */ + u32 rsv3_2; + + /* DW8 */ + u32 eqn : 8; + u32 eqe_cnt : 10; + u32 rsv4 : 12; + u32 eqe_report_timer_l : 2; + + /* DW9 */ + u32 eqe_report_timer_h; + + /* DW10 */ + u32 funid : 8; + u32 pi_bypass : 24; + + /* DW11 */ + u32 state2 : 2; + u32 rsv5_1 : 30; + + /* DW12~DW15 */ + u32 rsv5_2[4]; +}; + +struct ubase_ceq { + struct ubase_dev *udev; + struct ubase_eq eq; +}; + +struct ubase_aeq { + struct ubase_dev *udev; + struct ubase_eq eq; + struct ubase_event_nb cb[UBASE_AE_LEVEL_NUM]; +}; + +struct ubase_ceqs { + struct ubase_ceq *ceq; + u32 num; +}; + +struct ubase_irq_table { + struct mutex ceq_lock; + struct ubase_ceqs ceqs; + struct ubase_aeq aeq; + struct blocking_notifier_head nh[UBASE_DRV_MAX][UBASE_EVENT_TYPE_MAX]; + + struct ubase_irq **irqs; /* first one for misc */ + u32 irqs_num; +}; + +struct ubase_aeq_work { + struct ubase_dev *udev; + struct work_struct work; + struct ubase_aeqe aeqe; +}; + +int ubase_irq_table_init(struct ubase_dev *udev); +void ubase_irq_table_uninit(struct ubase_dev *udev); +void ubase_irq_table_free(struct ubase_dev *udev); + +int ubase_register_ae_event(struct ubase_dev *udev); +void ubase_unregister_ae_event(struct ubase_dev *udev); + +void ubase_enable_misc_vector(struct ubase_dev *udev, bool enable); +void ubase_disable_ce_irqs(struct ubase_dev *udev); +int ubase_enable_ce_irqs(struct ubase_dev *udev); + +#endif diff --git a/drivers/ub/ubase/ubase_err_handle.c b/drivers/ub/ubase/ubase_err_handle.c new file mode 100644 index 0000000000000000000000000000000000000000..d91ab0f80b6b4f430907aa5205c942afbbdf2d49 --- /dev/null +++ b/drivers/ub/ubase/ubase_err_handle.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include "ubase_cmd.h" +#include "ubase_dev.h" +#include "ubase_reset.h" +#include "ubase_err_handle.h" + +void ubase_errhandle_service_task(struct ubase_delay_work *ubase_work) +{ + struct ubase_dev *udev; + + udev = container_of(ubase_work, struct ubase_dev, service_task); + if (!test_and_clear_bit(UBASE_SERVICE_STATE_ERR_SCHED, + &ubase_work->state)) + return; + + if (!ubase_dev_err_handle_supported(udev)) { + ubase_err(udev, "not support err handle processing.\n"); + return; + } + + if (test_and_clear_bit(UBASE_STATE_PORT_RESETTING_B, &udev->state_bits)) { + ubase_err(udev, "ras occurred, ubase need to reset port.\n"); + ubase_port_reset(udev); + } +} diff --git a/drivers/ub/ubase/ubase_err_handle.h b/drivers/ub/ubase/ubase_err_handle.h new file mode 100644 index 0000000000000000000000000000000000000000..229e1d23b9ba1ba74456b2d7327d5983713360df --- /dev/null +++ b/drivers/ub/ubase/ubase_err_handle.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_ERR_HANDLE_H__ +#define __UBASE_ERR_HANDLE_H__ + +#include + +void ubase_errhandle_service_task(struct ubase_delay_work *ubase_work); + +#endif diff --git a/drivers/ub/ubase/ubase_hw.c b/drivers/ub/ubase/ubase_hw.c new file mode 100644 index 0000000000000000000000000000000000000000..d728dd47f116d0b3629777c789211c9d681732fc --- /dev/null +++ b/drivers/ub/ubase/ubase_hw.c @@ -0,0 +1,1028 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include +#include + +#include "ubase_cmd.h" +#include "ubase_ctrlq.h" +#include "ubase_dev.h" +#include "ubase_mailbox.h" +#include "ubase_tp.h" +#include "ubase_hw.h" + +static DEFINE_MUTEX(ubase_perf_mutex); + +struct ubase_dma_buf_desc { + struct ubase_dma_buf *buf; + u16 opc; + bool (*is_supported)(struct ubase_dev *dev); +}; + +#define UBASE_DEFINE_DMA_BUFS(udev) \ + struct ubase_dma_buf_desc bufs[] = { \ + { &(udev)->ta_ctx.extdb_buf, UBASE_OPC_TA_EXTDB_VA_CONFIG, \ + &ubase_dev_ta_extdb_buf_supported }, \ + { &(udev)->ta_ctx.timer_buf, UBASE_OPC_TA_TIMER_VA_CONFIG, \ + &ubase_dev_ta_timer_buf_supported } \ + } + +static void ubase_assign_addr_val(void *addr, u32 val, u8 size) +{ +#define VAL_SIZE_1 1 +#define VAL_SIZE_2 2 +#define VAL_SIZE_4 4 + + switch (size) { + case VAL_SIZE_1: + *(u8 *)addr = (u8)val; + break; + case VAL_SIZE_2: + *(u16 *)addr = (u16)val; + break; + case VAL_SIZE_4: + *(u32 *)addr = (u32)val; + break; + default: + break; + } +} + +static void ubase_check_dev_caps_comm(struct ubase_dev *udev) +{ + struct ubase_caps *caps = &udev->caps.dev_caps; + struct ubase_caps_item items[] = { + { + &caps->num_ceq_vectors, UBASE_DEF_CEQ_VECTOR_NUM, + sizeof(caps->num_ceq_vectors), "ceq vector num" + }, + { + &caps->num_aeq_vectors, UBASE_DEF_AEQ_VECTOR_NUM, + sizeof(caps->num_aeq_vectors), "aeq vector num" + }, + { + &caps->num_misc_vectors, UBASE_DEF_MISC_VERCTOR_NUM, + sizeof(caps->num_misc_vectors), "misc vector num" + }, + { + &caps->public_jetty_cnt, UBASE_DEF_PUBLIC_JETTY_CNT, + sizeof(caps->public_jetty_cnt), "public jetty count" + }, + { + &caps->aeqe_size, UBASE_DEF_EQE_SIZE, + sizeof(caps->aeqe_size), "aeqe size" + }, + { + &caps->ceqe_size, UBASE_DEF_EQE_SIZE, + sizeof(caps->ceqe_size), "ceqe size" + }, + { + &caps->aeqe_depth, UBASE_DEF_AEQ_DEPTH, + sizeof(caps->aeqe_depth), "aeqe depth" + }, + { + &caps->ceqe_depth, UBASE_DEF_CEQ_DEPTH, + sizeof(caps->ceqe_depth), "ceqe depth" + }, + }; + u32 i, items_size = ARRAY_SIZE(items); + u32 val; + + for (i = 0; i < items_size; i++) { + val = 0; + memcpy(&val, items[i].p, items[i].size); + if (!val) { + ubase_assign_addr_val(items[i].p, items[i].default_val, + items[i].size); + ubase_warn(udev, "using default %s(%u).\n", + items[i].name, items[i].default_val); + } + } +} + +static int ubase_check_dev_caps_extdb(struct ubase_dev *udev) +{ + UBASE_DEFINE_DMA_BUFS(udev); + int i; + + for (i = 0; i < ARRAY_SIZE(bufs); i++) { + if (bufs[i].is_supported(udev) && !bufs[i].buf->size) { + ubase_err(udev, + "failed to check caps: buf[%d] size=0.\n", i); + return -EINVAL; + } + } + + return 0; +} + +static int ubase_check_dev_caps(struct ubase_dev *udev) +{ + ubase_check_dev_caps_comm(udev); + + return ubase_check_dev_caps_extdb(udev); +} + +static void ubase_parse_dev_caps_comm(struct ubase_dev *udev, + const struct ubase_res_cmd_resp *resp) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + + dev_caps->num_ceq_vectors = le16_to_cpu(resp->ceq_vector_num); + dev_caps->num_aeq_vectors = le16_to_cpu(resp->aeq_vector_num); + dev_caps->num_misc_vectors = le16_to_cpu(resp->misc_vector_num); + dev_caps->aeqe_size = le16_to_cpu(resp->aeqe_size); + dev_caps->ceqe_size = le16_to_cpu(resp->ceqe_size); + dev_caps->aeqe_depth = le32_to_cpu(resp->aeqe_depth); + dev_caps->ceqe_depth = le32_to_cpu(resp->ceqe_depth); + dev_caps->total_ue_num = le32_to_cpu(resp->total_ue_num); + dev_caps->public_jetty_cnt = le32_to_cpu(resp->public_jetty_cnt); + dev_caps->rsvd_jetty_cnt = le16_to_cpu(resp->rsvd_jetty_cnt); + dev_caps->ue_num = resp->ue_num; + dev_caps->mac_stats_num = le16_to_cpu(resp->mac_stats_num); + + udev->ta_ctx.extdb_buf.size = le32_to_cpu(resp->ta_extdb_buf_size); + udev->ta_ctx.timer_buf.size = le32_to_cpu(resp->ta_timer_buf_size); +} + +static void ubase_parse_dev_caps_unic(struct ubase_dev *udev, + const struct ubase_res_cmd_resp *resp) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + + unic_caps->jfs.max_cnt = le32_to_cpu(resp->nic_jfs_max_cnt); + unic_caps->jfs.reserved_cnt = le32_to_cpu(resp->nic_jfs_reserved_cnt); + unic_caps->jfs.depth = le32_to_cpu(resp->nic_jfs_depth); + unic_caps->jfr.max_cnt = le32_to_cpu(resp->nic_jfr_max_cnt); + unic_caps->jfr.reserved_cnt = le32_to_cpu(resp->nic_jfr_reserved_cnt); + unic_caps->jfr.depth = le32_to_cpu(resp->nic_jfr_depth); + unic_caps->jfc.max_cnt = le32_to_cpu(resp->nic_jfc_max_cnt); + unic_caps->jfc.reserved_cnt = le32_to_cpu(resp->nic_jfc_reserved_cnt); + unic_caps->jfc.depth = le32_to_cpu(resp->nic_jfc_depth); + unic_caps->cqe_size = le16_to_cpu(resp->nic_cqe_size); + unic_caps->utp_port_bitmap = le32_to_cpu(resp->port_bitmap); +} + +static void ubase_parse_dev_caps_udma(struct ubase_dev *udev, + const struct ubase_res_cmd_resp *resp) +{ + struct ubase_adev_caps *udma_caps = &udev->caps.udma_caps; + + udma_caps->jfs.max_cnt = le32_to_cpu(resp->udma_jfs_max_cnt); + udma_caps->jfs.reserved_cnt = le32_to_cpu(resp->udma_jfs_reserved_cnt); + udma_caps->jfs.depth = le32_to_cpu(resp->udma_jfs_depth); + udma_caps->jfr.max_cnt = le32_to_cpu(resp->udma_jfr_max_cnt); + udma_caps->jfr.reserved_cnt = le32_to_cpu(resp->udma_jfr_reserved_cnt); + udma_caps->jfr.depth = le32_to_cpu(resp->udma_jfr_depth); + udma_caps->jfc.max_cnt = le32_to_cpu(resp->udma_jfc_max_cnt); + udma_caps->jfc.reserved_cnt = le32_to_cpu(resp->udma_jfc_reserved_cnt); + udma_caps->jfc.depth = le32_to_cpu(resp->udma_jfc_depth); + udma_caps->cqe_size = le16_to_cpu(resp->udma_cqe_size); + udma_caps->jtg_max_cnt = le32_to_cpu(resp->jtg_max_cnt); + udma_caps->rc_max_cnt = le32_to_cpu(resp->rc_max_cnt_per_vl); + udma_caps->rc_que_depth = le32_to_cpu(resp->udma_rc_depth); +} + +static void ubase_parse_dev_caps(struct ubase_dev *udev, + const struct ubase_res_cmd_resp *resp) +{ + int i; + + for (i = 0; i < UBASE_CAP_LEN; i++) + udev->cap_bits[i] = le32_to_cpu(resp->cap_bits[i]); + + ubase_parse_dev_caps_comm(udev, resp); + ubase_parse_dev_caps_unic(udev, resp); + ubase_parse_dev_caps_udma(udev, resp); +} + +static int ubase_parse_dev_res(struct ubase_dev *udev, + struct ubase_res_cmd_resp *resp) +{ + ubase_parse_dev_caps(udev, resp); + return ubase_check_dev_caps(udev); +} + +int ubase_query_dev_res(struct ubase_dev *udev) +{ + struct ubase_res_cmd_resp resp = {0}; + struct ubase_cmd_buf out; + struct ubase_cmd_buf in; + int ret; + + __ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_COMM_RSRC_PARAM, true, 0, + NULL); + + __ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_COMM_RSRC_PARAM, false, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, "failed to query ubase res, ret = %d.\n", ret); + return ret; + } + + return ubase_parse_dev_res(udev, &resp); +} + +static void ubase_init_start_idx(struct ubase_dev *udev) +{ + struct ubase_adev_caps *udma_caps = &udev->caps.udma_caps; + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + + unic_caps->jfs.start_idx = udev->caps.dev_caps.public_jetty_cnt + + udev->caps.dev_caps.rsvd_jetty_cnt; + + udma_caps->jfs.start_idx = unic_caps->jfs.max_cnt + + udev->caps.dev_caps.public_jetty_cnt + + udev->caps.dev_caps.rsvd_jetty_cnt; + udma_caps->jfr.start_idx = unic_caps->jfr.max_cnt; + udma_caps->jfc.start_idx = unic_caps->jfc.max_cnt; +} + +static int ubase_config_ctx_buf_to_hw(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + struct ubase_mbx_attr *attr) +{ + struct ubase_cmd_mailbox mailbox; + int ret; + + mailbox.dma = ctx_buf->dma_ctx_buf_ba; + ret = __ubase_hw_upgrade_ctx(udev, attr, &mailbox); + if (ret) + ubase_err(udev, + "failed to config ctx_buf to hw, cmd = 0x%x, ret = %d.\n", + attr->op, ret); + return ret; +} + +static void ubase_free_and_clear_ctx_buf(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf) +{ + struct ubase_ctx_page *ctx_page; + size_t npage; + + if (!xa_empty(&ctx_buf->ctx_xa)) { + xa_for_each(&ctx_buf->ctx_xa, npage, ctx_page) + ubase_destroy_ctx_page(udev, ctx_page, ctx_buf); + } + dma_free_iova(ctx_buf->slot); + + ctx_buf->slot = NULL; + ctx_buf->dma_ctx_buf_ba = 0; +} + +static void ubase_cmd_ctx_buf_free(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf) +{ + size_t size; + + if (!ctx_buf || !ctx_buf->slot) + return; + + size = ctx_buf->entry_cnt * ctx_buf->entry_size; + if (!size) + return; + + ubase_free_and_clear_ctx_buf(udev, ctx_buf); + xa_destroy(&ctx_buf->ctx_xa); +} + +static void ubase_ctx_free(struct ubase_dev *udev, + struct ubase_ctx_buf *ctx_buf, int idx) +{ + struct ubase_ctx_buf_map map[] = { + { &ctx_buf->jfs, UBASE_MB_WRITE_JFS_CONTEXT_VA }, + { &ctx_buf->jfr, UBASE_MB_WRITE_JFR_CONTEXT_VA }, + { &ctx_buf->jfc, UBASE_MB_WRITE_JFC_CONTEXT_VA }, + { &ctx_buf->jtg, UBASE_MB_WRITE_JETTY_GROUP_CONTEXT_VA }, + { &ctx_buf->rc, UBASE_MB_WRITE_RC_CONTEXT_VA }, + }; + int i, end_idx = ARRAY_SIZE(map) - 1; + + i = (idx == UBASE_CTX_REMOVE_ALL) ? end_idx : idx; + for (; i >= 0; i--) + ubase_cmd_ctx_buf_free(udev, map[i].ctx); +} + +static int ubase_fill_common_ctx_buf(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + u32 start_pos, u32 size) +{ + struct ubase_ctx_page *ctx_page; + size_t npage; + int ret; + u32 i; + + mutex_lock(&ctx_buf->ctx_mutex); + + for (i = start_pos; i < start_pos + size; i++) { + ctx_page = (struct ubase_ctx_page *)xa_load(&ctx_buf->ctx_xa, i); + if (ctx_page) + continue; + + ret = ubase_create_ctx_page(udev, ctx_buf, &ctx_page, i); + if (ret) { + ubase_err(udev, "failed to create context page, ret = %d.\n", + ret); + goto err_fill_ctx_page; + } + + ret = xa_err(xa_store(&ctx_buf->ctx_xa, i, ctx_page, + GFP_KERNEL)); + if (ret) { + ubase_err(udev, "failed to store page, ret = %d.\n", + ret); + goto err_fill_ctx_page; + } + } + + mutex_unlock(&ctx_buf->ctx_mutex); + + return 0; + +err_fill_ctx_page: + xa_for_each(&ctx_buf->ctx_xa, npage, ctx_page) + ubase_destroy_ctx_page(udev, ctx_page, ctx_buf); + mutex_unlock(&ctx_buf->ctx_mutex); + + return ret; +} + +static int ubase_fill_ctx_inherent_buf(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + struct ubase_mbx_attr *attr) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + u32 buf_size, page_cnt; + u32 start_pos = 0; + + switch (attr->op) { + case UBASE_MB_WRITE_JFS_CONTEXT_VA: + start_pos = udev->caps.unic_caps.jfs.start_idx >> + ctx_buf->cnt_per_page_shift; + buf_size = unic_caps->jfs.max_cnt * ctx_buf->entry_size; + break; + case UBASE_MB_WRITE_JFR_CONTEXT_VA: + buf_size = unic_caps->jfr.max_cnt * ctx_buf->entry_size; + break; + case UBASE_MB_WRITE_JFC_CONTEXT_VA: + buf_size = unic_caps->jfc.max_cnt * ctx_buf->entry_size; + break; + default: + return 0; + } + + page_cnt = DIV_ROUND_UP(buf_size, PAGE_SIZE); + return ubase_fill_common_ctx_buf(udev, ctx_buf, start_pos, page_cnt); +} + +static int ubase_alloc_and_fill_ctx_buf(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + struct ubase_mbx_attr *attr, + size_t size) +{ + size_t sizep; + int ret; + + ctx_buf->cnt_per_page_shift = + ilog2(roundup_pow_of_two(PAGE_SIZE / ctx_buf->entry_size)); + ctx_buf->slot = dma_alloc_iova(udev->dev, size, 0, + &ctx_buf->dma_ctx_buf_ba, &sizep); + if (IS_ERR(ctx_buf->slot)) { + ubase_err(udev, + "failed to alloc iova slot, cmd = 0x%x, size = %lu.\n", + attr->op, size); + return -ENOMEM; + } + + ret = ubase_fill_ctx_inherent_buf(udev, ctx_buf, attr); + if (ret) { + ubase_err(udev, + "failed to fill inherent ctx buf, cmd = 0x%x, ret = %d.\n", + attr->op, ret); + dma_free_iova(ctx_buf->slot); + } + + return ret; +} + +static int ubase_config_jfs_ctx_buf_by_pmem(struct ubase_dev *udev, + struct ubase_mbx_attr *attr) +{ + struct ubase_cmd_mailbox mailbox; + int ret; + + mailbox.dma = udev->pmem_info.comm.dma_addr; + ret = __ubase_hw_upgrade_ctx(udev, attr, &mailbox); + if (ret) + ubase_err(udev, + "failed to config jfs ctx buf, ret = %d.\n", ret); + + return ret; +} + +static int ubase_cmd_ctx_buf_alloc(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + struct ubase_mbx_attr *attr) +{ + size_t size = ctx_buf->entry_cnt * ctx_buf->entry_size; + int ret; + + if (!size) + return 0; + + if (attr->op == UBASE_MB_WRITE_JFS_CONTEXT_VA && + test_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits)) + return ubase_config_jfs_ctx_buf_by_pmem(udev, attr); + + xa_init(&ctx_buf->ctx_xa); + ret = ubase_alloc_and_fill_ctx_buf(udev, ctx_buf, attr, size); + if (ret) + goto err_ctx_alloc; + + ret = ubase_config_ctx_buf_to_hw(udev, ctx_buf, attr); + if (ret) + goto err_ctx_to_hw; + + return 0; + +err_ctx_to_hw: + ubase_free_and_clear_ctx_buf(udev, ctx_buf); + +err_ctx_alloc: + xa_destroy(&ctx_buf->ctx_xa); + + return ret; +} + +static int ubase_ctx_alloc(struct ubase_dev *udev, + struct ubase_ctx_buf *ctx_buf) +{ + struct ubase_ctx_buf_map map[] = { + { &ctx_buf->jfs, UBASE_MB_WRITE_JFS_CONTEXT_VA }, + { &ctx_buf->jfr, UBASE_MB_WRITE_JFR_CONTEXT_VA }, + { &ctx_buf->jfc, UBASE_MB_WRITE_JFC_CONTEXT_VA }, + { &ctx_buf->jtg, UBASE_MB_WRITE_JETTY_GROUP_CONTEXT_VA }, + { &ctx_buf->rc, UBASE_MB_WRITE_RC_CONTEXT_VA }, + }; + struct ubase_mbx_attr attr = {0}; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(map); i++) { + memset(&attr, 0, sizeof(attr)); + attr.op = map[i].mb_cmd; + ret = ubase_cmd_ctx_buf_alloc(udev, map[i].ctx, &attr); + if (ret) { + ubase_err(udev, + "failed to alloc ctx buf, mb_cmd = 0x%x, ret = %d.\n", + map[i].mb_cmd, ret); + goto err_alloc_ctx_buf; + } + } + + return 0; + +err_alloc_ctx_buf: + ubase_ctx_free(udev, ctx_buf, --i); + return ret; +} + +static void ubase_get_ctx_entry_cnt(struct ubase_dev *udev) +{ + struct ubase_adev_caps *unic_caps = &udev->caps.unic_caps; + struct ubase_adev_caps *udma_caps = &udev->caps.udma_caps; + struct ubase_ctx_buf *ubase_ctx_buf = &udev->ctx_buf; + + ubase_ctx_buf->jfs.entry_cnt = ubase_jfs_num(udev); + ubase_ctx_buf->jfr.entry_cnt = unic_caps->jfr.max_cnt; + ubase_ctx_buf->jfc.entry_cnt = unic_caps->jfc.max_cnt; + ubase_ctx_buf->jtg.entry_cnt = udma_caps->jtg_max_cnt; + ubase_ctx_buf->rc.entry_cnt = udma_caps->rc_max_cnt; + + ubase_ctx_buf->jfr.entry_cnt += udma_caps->jfr.max_cnt; + ubase_ctx_buf->jfc.entry_cnt += udma_caps->jfc.max_cnt; +} + +static void ubase_get_ctx_entry_size(struct ubase_dev *udev) +{ + struct ubase_ctx_buf *ubase_ctx_buf = &udev->ctx_buf; + + ubase_ctx_buf->jfs.entry_size = UBASE_JFS_CTX_SIZE; + ubase_ctx_buf->jfr.entry_size = UBASE_JFR_CTX_SIZE; + ubase_ctx_buf->jfc.entry_size = UBASE_JFC_CTX_SIZE; + ubase_ctx_buf->jtg.entry_size = UBASE_JTG_CTX_SIZE; + ubase_ctx_buf->rc.entry_size = UBASE_RC_CTX_SIZE; +} + +static int ubase_ctx_buf_alloc(struct ubase_dev *udev) +{ + struct ubase_ctx_buf *ctx_buf = &udev->ctx_buf; + + ubase_get_ctx_entry_cnt(udev); + ubase_get_ctx_entry_size(udev); + + return ubase_ctx_alloc(udev, ctx_buf); +} + +static int ubase_config_dma_buf(struct ubase_dev *udev, u16 opc, u64 dma_addr) +{ + struct ubase_cfg_dma_buf_req req = {0}; + struct ubase_cmd_buf in; + int ret; + + req.addr_l = cpu_to_le32(lower_32_bits(dma_addr)); + req.addr_h = cpu_to_le32(upper_32_bits(dma_addr)); + + __ubase_fill_inout_buf(&in, opc, false, sizeof(req), &req); + + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, + "failed to send cmd in cfg dma buf, ret = %d.\n", ret); + + return ret; +} + +static int ubase_config_ta_timer_buf_by_pmem(struct ubase_dev *udev, u16 opc) +{ + u64 dma_addr, offset; + + offset = ubase_jfs_ctx_align_size(udev); + dma_addr = udev->pmem_info.comm.dma_addr + offset; + + return ubase_config_dma_buf(udev, opc, dma_addr); +} + +static int ubase_init_dma_buf(struct ubase_dev *udev, struct ubase_dma_buf *buf, + u16 opc) +{ + int ret; + + if (opc == UBASE_OPC_TA_TIMER_VA_CONFIG && + test_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits)) + return ubase_config_ta_timer_buf_by_pmem(udev, opc); + + buf->addr = dma_alloc_coherent(udev->dev, buf->size, &buf->dma_addr, + GFP_KERNEL); + if (!buf->addr) + return -ENOMEM; + + ret = ubase_config_dma_buf(udev, opc, buf->dma_addr); + if (ret) + dma_free_coherent(udev->dev, buf->size, buf->addr, + buf->dma_addr); + + return ret; +} + +static void ubase_uninit_dma_buf(struct ubase_dev *udev, + struct ubase_dma_buf *buf) +{ + if (!buf->addr) + return; + + dma_free_coherent(udev->dev, buf->size, buf->addr, buf->dma_addr); + buf->addr = NULL; +} + +static int ubase_init_ta_tp_ext_buf(struct ubase_dev *udev) +{ + UBASE_DEFINE_DMA_BUFS(udev); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(bufs); i++) { + ret = bufs[i].is_supported(udev) ? + ubase_init_dma_buf(udev, bufs[i].buf, bufs[i].opc) : 0; + if (ret) { + ubase_err(udev, + "failed to init buf[%d], ret = %d.\n", i, ret); + goto err_out; + } + } + + return 0; +err_out: + for (i -= 1; i >= 0; i--) { + if (bufs[i].is_supported(udev)) + ubase_uninit_dma_buf(udev, bufs[i].buf); + } + return ret; +} + +static void ubase_uninit_ta_tp_ext_buf(struct ubase_dev *udev) +{ + UBASE_DEFINE_DMA_BUFS(udev); + int i; + + for (i = 0; i < ARRAY_SIZE(bufs); i++) { + if (bufs[i].is_supported(udev)) + ubase_uninit_dma_buf(udev, bufs[i].buf); + } +} + +int ubase_query_hw_oor_caps(struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_query_oor_resp resp = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_OOR_CAPS, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_OOR_CAPS, true, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to query hw oor caps, ret = %d.\n", ret); + return ret; + } + + dev_caps->oor_en = resp.oor_en; + dev_caps->reorder_queue_en = resp.reorder_cq_buffer_en; + dev_caps->reorder_cap = resp.reorder_cap; + dev_caps->reorder_queue_shift = resp.reorder_cq_shift; + dev_caps->on_flight_size = le32_to_cpu(resp.on_flight_size); + dev_caps->at_times = resp.dynamic_ack_timeout; + + return 0; +} + +int ubase_query_port_bitmap(struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_query_port_bitmap_resp resp = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_PORT_BITMAP, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_PORT_BITMAP, true, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret && ret != -EPERM) { + dev_err(udev->dev, + "failed to query port bitmap, ret = %d.\n", ret); + return ret; + } + + dev_caps->logic_port_bitmap = le32_to_cpu(resp.logic_port_bitmap); + + return 0; +} + +int ubase_query_controller_info(struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_query_controller_info_resp resp = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_CTL_INFO, true, 0, NULL); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_CTL_INFO, true, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to query controller info, ret = %d.\n", ret); + return ret; + } + + dev_caps->packet_pattern_mode = resp.packet_pattern_mode; + dev_caps->ack_queue_num = resp.ack_queue_num; + + return 0; +} + +int ubase_query_chip_info(struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_query_chip_die_cmd resp = {0}; + struct ubase_cmd_buf in, out; + int ret; + + __ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_CHIP_INFO, true, 0, NULL); + __ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_CHIP_INFO, true, + sizeof(resp), &resp); + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to query ub chip info, ret = %d.\n", ret); + return ret; + } + + dev_caps->nl_port_id = le16_to_cpu(resp.nl_port_id); + dev_caps->chip_id = le16_to_cpu(resp.chip_id); + dev_caps->die_id = le16_to_cpu(resp.die_id); + dev_caps->io_port_id = le16_to_cpu(resp.io_port_id); + dev_caps->ue_id = le16_to_cpu(resp.ue_id); + dev_caps->ub_port_logic_id = le16_to_cpu(resp.ub_port_logic_id); + dev_caps->io_port_logic_id = le16_to_cpu(resp.io_port_logic_id); + dev_caps->nl_id = le16_to_cpu(resp.nl_id); + + return 0; +} + +static void ubase_destroy_ctx_res(struct ubase_dev *udev) +{ +#define UBASE_DESTROY_RES_WAIT_TIME 20 +#define UBASE_DESTROY_RES_WAIT_COUNT 5 + + struct ubase_destroy_res_cmd resp; + struct ubase_cmd_buf in, out; + int try_cnt = 0; + int ret; + + __ubase_fill_inout_buf(&in, UBASE_OPC_DESTROY_CTX_RESOURCE, false, 0, NULL); + ret = __ubase_cmd_send_in(udev, &in); + if (ret) { + ubase_err(udev, "failed to send destroy resource, ret = %d.\n", + ret); + return; + } + + __ubase_fill_inout_buf(&in, UBASE_OPC_DESTROY_CTX_RESOURCE, true, 0, NULL); + __ubase_fill_inout_buf(&out, UBASE_OPC_DESTROY_CTX_RESOURCE, false, + sizeof(resp), &resp); + do { + memset(&resp, 0, sizeof(resp)); + msleep(UBASE_DESTROY_RES_WAIT_TIME); + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to query destroy resource, ret = %d.\n", + ret); + return; + } + + try_cnt++; + } while (!resp.destroy_done && try_cnt < UBASE_DESTROY_RES_WAIT_COUNT); + + if (!resp.destroy_done) + ubase_warn(udev, "wait ue destroy res timeout!\n"); +} + +static inline void ubase_uninit_ctx_buf(struct ubase_dev *udev) +{ + ubase_ctx_free(udev, &udev->ctx_buf, UBASE_CTX_REMOVE_ALL); +} + +static void ubase_init_ctx_buf_lock(struct ubase_ctx_buf *ctx_buf) +{ + struct mutex *ctx_mutex[] = { &ctx_buf->jfs.ctx_mutex, + &ctx_buf->jfr.ctx_mutex, + &ctx_buf->jfc.ctx_mutex, + &ctx_buf->jtg.ctx_mutex, + &ctx_buf->rc.ctx_mutex }; + int i; + + for (i = 0; i < ARRAY_SIZE(ctx_mutex); i++) + mutex_init(ctx_mutex[i]); +} + +static void ubase_uninit_ctx_buf_lock(struct ubase_ctx_buf *ctx_buf) +{ + struct mutex *ctx_mutex[] = { &ctx_buf->jfs.ctx_mutex, + &ctx_buf->jfr.ctx_mutex, + &ctx_buf->jfc.ctx_mutex, + &ctx_buf->jtg.ctx_mutex, + &ctx_buf->rc.ctx_mutex }; + int i; + + for (i = 0; i < ARRAY_SIZE(ctx_mutex); i++) + mutex_destroy(ctx_mutex[i]); +} + +int ubase_ue_init(struct ubase_dev *udev) +{ + ubase_init_start_idx(udev); + INIT_LIST_HEAD(&udev->ue_list); + mutex_init(&udev->ue_list_lock); + mutex_init(&udev->stats.stats_lock); + mutex_init(&udev->stats.activate_record.lock); + spin_lock_init(&udev->tp_ctx.tpg_lock); + mutex_init(&udev->act_ctx.lock); + init_completion(&udev->act_ctx.self.activate_done); + init_completion(&udev->act_ctx.other.activate_done); + ubase_init_ctx_buf_lock(&udev->ctx_buf); + + return 0; +} + +void ubase_ue_uninit(struct ubase_dev *udev) +{ + mutex_destroy(&udev->act_ctx.lock); + mutex_destroy(&udev->stats.activate_record.lock); + mutex_destroy(&udev->stats.stats_lock); + mutex_destroy(&udev->ue_list_lock); + ubase_uninit_ctx_buf_lock(&udev->ctx_buf); +} + +static int ubase_notify_ctrl_plane_init_res(struct ubase_dev *udev) +{ + struct ubase_ctrlq_reset_ctrl_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + u32 resp; + int ret; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_DEV_REGISTER; + msg.opcode = UBASE_CTRLQ_OPC_UE_RESET_CTRL; + msg.need_resp = 1; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + + req.flag = UBASE_CTRL_PLANE_INIT_RES; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) + dev_err(udev->dev, + "failed to notify ctrl plane init res, ret = %d.\n", + ret); + + return ret; +} + +int ubase_hw_init(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_notify_ctrl_plane_init_res(udev); + if (ret) + return ret; + + ret = ubase_ctx_buf_alloc(udev); + if (ret) { + ubase_err(udev, "failed to init ctx buf, ret = %d.\n", ret); + return ret; + } + + ret = ubase_init_ta_tp_ext_buf(udev); + if (ret) + goto err_init_ta_tp_ext_buf; + + ret = ubase_dev_init_tp_tpg(udev); + if (ret) { + ubase_err(udev, "failed to init tp & tpg, ret = %d.\n", ret); + goto err_init_tp_tpg; + } + + set_bit(UBASE_STATE_CTX_READY_B, &udev->state_bits); + + return 0; + +err_init_tp_tpg: + ubase_uninit_ta_tp_ext_buf(udev); +err_init_ta_tp_ext_buf: + ubase_uninit_ctx_buf(udev); + + return ret; +} + +void ubase_hw_uninit(struct ubase_dev *udev) +{ + clear_bit(UBASE_STATE_CTX_READY_B, &udev->state_bits); + + ubase_dev_uninit_tp_tpg(udev); + ubase_uninit_ta_tp_ext_buf(udev); + + if (!test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + ubase_ctrlq_disable_remote(udev); + ubase_destroy_ctx_res(udev); + } + + ubase_uninit_ctx_buf(udev); +} + +static int ubase_start_perf_stats(struct ubase_dev *udev, u32 period, + u64 port_bitmap) +{ + struct ubase_start_perf_stats_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + req.period = cpu_to_le32(period); + req.logic_port_bitmap[0] = cpu_to_le32(lower_32_bits(port_bitmap)); + req.logic_port_bitmap[1] = cpu_to_le32(upper_32_bits(port_bitmap)); + + __ubase_fill_inout_buf(&in, UBASE_OPC_START_PERF_STATS, false, + sizeof(req), &req); + ret = __ubase_cmd_send_in(udev, &in); + if (ret && ret != -EPERM) + ubase_err(udev, "failed to cfg perf stats period, ret = %d.\n", + ret); + + return ret == -EPERM ? -EOPNOTSUPP : ret; +} + +static int ubase_stop_perf_stats(struct ubase_dev *udev, + struct ubase_stop_perf_stats_cmd *resp, + u32 period, u16 port_id) +{ + struct ubase_stop_perf_stats_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.period = cpu_to_le32(period); + req.port_id = cpu_to_le16(port_id); + + __ubase_fill_inout_buf(&in, UBASE_OPC_STOP_PERF_STATS, true, + sizeof(req), &req); + __ubase_fill_inout_buf(&out, UBASE_OPC_STOP_PERF_STATS, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret && ret != -EPERM) + ubase_err(udev, "failed to query perf stats, ret = %d.\n", ret); + + return ret == -EPERM ? -EOPNOTSUPP : ret; +} + +int __ubase_perf_stats(struct ubase_dev *udev, u64 port_bitmap, u32 period, + struct ubase_perf_stats_result *data, u32 data_size) +{ +#define UBASE_MS_TO_US(ms) (1000 * (ms)) + struct ubase_stop_perf_stats_cmd resp = {0}; + unsigned long logic_port_bitmap; + int ret, j, k, port_num; + u8 i; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return -EBUSY; + + logic_port_bitmap = udev->caps.dev_caps.logic_port_bitmap; + + if (port_bitmap) { + if (data_size < bitmap_weight((unsigned long *)&port_bitmap, + UBASE_MAX_PORT_NUM) || + !bitmap_subset((unsigned long *)&port_bitmap, + &logic_port_bitmap, + UBASE_MAX_PORT_NUM)) + return -EINVAL; + } else { + if (data_size != UBASE_MAX_PORT_NUM) + return -EINVAL; + + port_bitmap = logic_port_bitmap; + } + + mutex_lock(&ubase_perf_mutex); + ret = ubase_start_perf_stats(udev, period, port_bitmap); + if (ret) + goto unlock; + + usleep_range(UBASE_MS_TO_US(period), UBASE_MS_TO_US(period + 1)); + + port_num = bitmap_weight((unsigned long *)&port_bitmap, + UBASE_MAX_PORT_NUM); + for (i = 0, k = 0; i < UBASE_MAX_PORT_NUM && k < port_num; i++) { + if (!test_bit(i, (unsigned long *)&port_bitmap)) + continue; + ret = ubase_stop_perf_stats(udev, &resp, period, i); + if (ret) + goto unlock; + + data[k].tx_port_bw = le32_to_cpu(resp.tx_port_bw); + data[k].rx_port_bw = le32_to_cpu(resp.rx_port_bw); + data[k].port_id = i; + data[k].valid = 1; + + for (j = 0; j < UBASE_STATS_MAX_VL_NUM; j++) { + data[k].tx_vl_bw[j] = le32_to_cpu(resp.tx_vl_bw[j]); + data[k].rx_vl_bw[j] = le32_to_cpu(resp.rx_vl_bw[j]); + } + + k++; + } + +unlock: + mutex_unlock(&ubase_perf_mutex); + + return ret; +} + +int ubase_perf_stats(struct auxiliary_device *adev, u64 port_bitmap, u32 period, + struct ubase_perf_stats_result *data, u32 data_size) +{ + struct ubase_dev *udev; + + if (!adev || !data || !period || !data_size) + return -EINVAL; + + udev = ubase_get_udev_by_adev(adev); + if (!(ubase_dev_ubl_supported(udev) || ubase_dev_fwctl_supported(udev))) + return -EOPNOTSUPP; + + return __ubase_perf_stats(udev, port_bitmap, period, data, data_size); +} +EXPORT_SYMBOL(ubase_perf_stats); diff --git a/drivers/ub/ubase/ubase_hw.h b/drivers/ub/ubase/ubase_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..ee455ba317d1e73c01221abbb9eb27da9e57093d --- /dev/null +++ b/drivers/ub/ubase/ubase_hw.h @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_HW_H__ +#define __UBASE_HW_H__ + +#include + +#include "ubase_cmd.h" + +#define UBASE_CTX_REMOVE_ALL (-2) + +#define UBASE_DEF_CEQ_VECTOR_NUM 1 +#define UBASE_DEF_AEQ_VECTOR_NUM 1 +#define UBASE_DEF_MISC_VERCTOR_NUM 1 +#define UBASE_DEF_PUBLIC_JETTY_CNT 1024 +#define UBASE_DEF_EQE_SIZE 64 +#define UBASE_DEF_AEQ_DEPTH 512 +#define UBASE_DEF_CEQ_DEPTH 4096 + +struct ubase_caps_item { + void *p; + u32 default_val; + u8 size; + const char *name; +}; + +struct ubase_res_cmd_resp { + __le32 cap_bits[UBASE_CAP_LEN]; + __le32 rsvd[3]; + + u8 rsvd1[2]; + __le16 ceq_vector_num; + __le16 aeq_vector_num; + __le16 misc_vector_num; + __le16 aeqe_size; + __le16 ceqe_size; + __le16 udma_cqe_size; + __le16 nic_cqe_size; + __le32 aeqe_depth; + __le32 ceqe_depth; + __le32 udma_jfs_max_cnt; + __le32 udma_jfs_reserved_cnt; + + __le32 udma_jfs_depth; + __le32 udma_jfr_max_cnt; + __le32 udma_jfr_reserved_cnt; + __le32 udma_jfr_depth; + u8 nic_vl_num; + u8 rsvd2[3]; + u8 nic_vl[UBASE_MAX_REQ_VL_NUM]; + __le32 udma_jfc_max_cnt; + + __le32 udma_jfc_reserved_cnt; + __le32 udma_jfc_depth; + __le32 udma_tp_max_cnt; + __le32 udma_tp_reserved_cnt; + __le32 udma_tp_depth; + __le32 udma_tpg_max_cnt; + __le32 udma_tpg_reserved_cnt; + __le32 udma_tpg_depth; + + __le32 nic_jfs_max_cnt; + __le32 nic_jfs_reserved_cnt; + __le32 nic_jfs_depth; + __le32 nic_jfr_max_cnt; + __le32 nic_jfr_reserved_cnt; + __le32 nic_jfr_depth; + __le32 rsvd3[2]; + + __le32 rsvd4; + __le32 nic_jfc_max_cnt; + __le32 nic_jfc_reserved_cnt; + __le32 nic_jfc_depth; + __le32 nic_tp_max_cnt; + __le32 nic_tp_reserved_cnt; + __le32 nic_tp_depth; + __le32 nic_tpg_max_cnt; + + __le32 nic_tpg_reserved_cnt; + __le32 nic_tpg_depth; + __le32 total_ue_num; + __le32 jfs_ctx_size; + __le32 jfr_ctx_size; + __le32 jfc_ctx_size; + __le32 tp_ctx_size; + __le16 rsvd_jetty_cnt; + __le16 mac_stats_num; + + __le32 ta_extdb_buf_size; + __le32 ta_timer_buf_size; + __le32 public_jetty_cnt; + __le32 tp_extdb_buf_size; + __le32 tp_timer_buf_size; + u8 port_work_mode; + u8 udma_vl_num; + u8 udma_tp_resp_vl_offset; + u8 ue_num; + __le32 port_bitmap; + u8 rsvd5[4]; + + /* include udma tp and ctp req vl */ + u8 udma_req_vl[UBASE_MAX_REQ_VL_NUM]; + __le32 udma_rc_depth; + u8 rsvd6[4]; + __le32 jtg_max_cnt; + __le32 rc_max_cnt_per_vl; + __le32 dest_addr_max_cnt; + __le32 seid_upi_max_cnt; + + __le32 tpm_max_cnt; + __le32 ccc_max_cnt; +}; + +struct ubase_query_oor_resp { + u8 oor_en; + u8 reorder_cq_buffer_en; + u8 reorder_cap; + u8 reorder_cq_shift; + __le32 on_flight_size; + u8 dynamic_ack_timeout; + u8 rsvd0[15]; +}; + +struct ubase_query_port_bitmap_resp { + __le32 logic_port_bitmap; + __le32 chip_id; + __le32 die_id; + __le32 resv[3]; +}; + +struct ubase_query_controller_info_resp { + __le32 rsvd0[2]; + u8 packet_pattern_mode : 1; + u8 ack_queue_num : 4; + u8 rsvd1 : 3; + u8 rsvd2[15]; +}; + +struct ubase_cfg_dma_buf_req { + __le32 addr_l; + __le32 addr_h; + __le32 tp_num; /* only used when cfg TP extdb buf */ + __le32 resv[3]; +}; + +struct ubase_config_sl_vl_cmd { + u8 sl_num; + u8 sl_vl[23]; +}; + +struct ubase_query_chip_die_cmd { + __le16 nl_port_id; + __le16 chip_id; + __le16 die_id; + __le16 io_port_id; + __le16 ue_id; + __le16 ub_port_logic_id; + __le16 nl_id; + __le16 io_port_logic_id; +}; + +struct ubase_ctx_buf_map { + struct ubase_ctx_buf_cap *ctx; + u16 mb_cmd; +}; + +struct ubase_query_vl_ageing_cmd { + __le16 vl_ageing_en; + u8 rsv[22]; +}; + +struct ubase_query_ctp_vl_offset_cmd { + u8 ctp_vl_offset; + u8 rsv[23]; +}; + +int ubase_hw_init(struct ubase_dev *udev); +void ubase_hw_uninit(struct ubase_dev *udev); +int ubase_query_sl_vl_map(struct ubase_dev *udev, u8 *sl_vl); +int ubase_qos_init(struct ubase_dev *udev); +int ubase_query_ets_tc(struct ubase_dev *udev, u32 port_bitmap, + u16 vl_bitmap, struct ubase_cfg_ets_vl_sch_cmd *resp); +int ubase_query_ets_tcg(struct ubase_dev *udev, + struct ubase_query_ets_tcg_cmd *resp); +int ubase_query_ets_port(struct ubase_dev *udev, + struct ubase_query_ets_port_cmd *resp); +int ubase_query_dev_res(struct ubase_dev *udev); +int ubase_query_chip_info(struct ubase_dev *udev); +int ubase_query_controller_info(struct ubase_dev *udev); +int ubase_query_hw_oor_caps(struct ubase_dev *udev); +int ubase_query_tm_queue(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_queue_cmd *resp); +int ubase_query_tm_qset(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_qset_cmd *resp); +int ubase_query_tm_pri(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_pri_cmd *resp); +int ubase_query_tm_pg(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_pg_cmd *resp); +int ubase_query_tm_port(struct ubase_dev *udev, + struct ubase_query_tm_port_cmd *resp); +int ubase_ue_init(struct ubase_dev *udev); +void ubase_ue_uninit(struct ubase_dev *udev); +int ubase_query_fst_fvt_rqmt(struct ubase_dev *udev, + struct ubase_query_fst_fvt_rqmt_cmd *resp, + u16 bus_ue_id); +int ubase_query_port_bitmap(struct ubase_dev *udev); +int __ubase_perf_stats(struct ubase_dev *udev, u64 port_bitmap, u32 period, + struct ubase_perf_stats_result *data, u32 data_size); + +#endif diff --git a/drivers/ub/ubase/ubase_mailbox.c b/drivers/ub/ubase/ubase_mailbox.c new file mode 100644 index 0000000000000000000000000000000000000000..ff3ff78ca513cc3617a6fb92f1de1f15c51bd3a5 --- /dev/null +++ b/drivers/ub/ubase/ubase_mailbox.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include + +#include "ubase_cmd.h" +#include "ubase_mailbox.h" + +int ubase_mbox_cmd_init(struct ubase_dev *udev) +{ + struct ubase_mbx_event_context *ctx = &udev->mb_cmd.ctx; + + udev->mb_cmd.pool = dma_pool_create("ubase_mbox", udev->dev, + UBASE_MAILBOX_SIZE, + UBASE_MAILBOX_SIZE, 0); + if (!udev->mb_cmd.pool) + return -ENOMEM; + + sema_init(&udev->mb_cmd.sem, 1); + init_completion(&ctx->done); + + return 0; +} + +void ubase_mbox_cmd_uninit(struct ubase_dev *udev) +{ + if (!udev->mb_cmd.pool) + return; + + dma_pool_destroy(udev->mb_cmd.pool); + udev->mb_cmd.pool = NULL; +} + +struct ubase_cmd_mailbox *__ubase_alloc_cmd_mailbox(struct ubase_dev *udev) +{ + struct ubase_cmd_mailbox *mailbox; + + if (!udev->mb_cmd.pool) { + ubase_err(udev, "failed to alloc mailbox, pool is null.\n"); + return NULL; + } + + mailbox = kzalloc(sizeof(*mailbox), GFP_KERNEL); + if (!mailbox) { + ubase_err(udev, "failed to alloc mailbox.\n"); + goto failed_alloc_mailbox; + } + + mailbox->buf = dma_pool_zalloc(udev->mb_cmd.pool, GFP_KERNEL, + &mailbox->dma); + if (!mailbox->buf) { + ubase_err(udev, "failed to alloc buffer of mailbox.\n"); + goto failed_alloc_mailbox_buf; + } + + return mailbox; + +failed_alloc_mailbox_buf: + kfree(mailbox); +failed_alloc_mailbox: + return NULL; +} + +struct ubase_cmd_mailbox *ubase_alloc_cmd_mailbox(struct auxiliary_device *aux_dev) +{ + struct ubase_dev *udev; + + if (!aux_dev) + return NULL; + + udev = __ubase_get_udev_by_adev(aux_dev); + + return __ubase_alloc_cmd_mailbox(udev); +} +EXPORT_SYMBOL(ubase_alloc_cmd_mailbox); + +void __ubase_free_cmd_mailbox(struct ubase_dev *udev, + struct ubase_cmd_mailbox *mailbox) +{ + if (!mailbox) { + ubase_err(udev, "Invalid mailbox.\n"); + return; + } + + dma_pool_free(udev->mb_cmd.pool, mailbox->buf, mailbox->dma); + kfree(mailbox); +} + +void ubase_free_cmd_mailbox(struct auxiliary_device *aux_dev, + struct ubase_cmd_mailbox *mailbox) +{ + struct ubase_dev *udev; + + if (!aux_dev) + return; + + udev = __ubase_get_udev_by_adev(aux_dev); + + __ubase_free_cmd_mailbox(udev, mailbox); +} +EXPORT_SYMBOL(ubase_free_cmd_mailbox); + +static int ubase_post_mailbox(struct ubase_dev *udev, + struct ubase_cmdq_desc *desc, + u32 timeout, u8 *complete_status, bool is_read) +{ + union ubase_mbox *mb; + unsigned long end; + int ret; + + end = msecs_to_jiffies(timeout) + jiffies; + mb = (union ubase_mbox *)desc->data; + + while (1) { + desc->flag = cpu_to_le16(UBASE_CMD_FLAG_NO_INTR | UBASE_CMD_FLAG_IN); + if (is_read) + desc->flag |= cpu_to_le16(UBASE_CMD_FLAG_WR); + + ret = ubase_send_cmd(udev, desc, 1); + if (!ret && !(is_read ? mb->query_hw_run : mb->hw_run)) + break; + + if (time_after(jiffies, end)) { + dev_err_ratelimited(udev->dev, + "failed to wait mbox.\n"); + return -ETIMEDOUT; + } + + cond_resched(); + } + + if (!ret) + *complete_status = + (u8)(is_read ? mb->query_status : mb->status); + + return ret; +} + +static int ubase_poll_mbox_done(struct ubase_dev *udev, uint32_t timeout) +{ + struct ubase_cmdq_desc desc; + u8 status = 0; + int ret; + + ubase_cmd_setup_basic_desc(&desc, UBASE_OPC_QUERY_MB_ST, true, 1); + + ret = ubase_post_mailbox(udev, &desc, timeout, &status, true); + if (!ret) { + if (!status) { + ubase_info(udev, + "failed to query ubase mailbox, status = %u.\n", + status); + return -EBUSY; + } + } else + dev_err_ratelimited(udev->dev, + "failed to check mbox status = %u, ret = %d.\n", + status, ret); + + return ret; +} + +static void ubase_mbox_desc_init(struct ubase_dev *udev, union ubase_mbox *mb, + u64 in_param, struct ubase_mbx_attr *attr) +{ + memset(mb, 0, sizeof(*mb)); + mb->in_param_l = cpu_to_le32(lower_32_bits(in_param)); + mb->in_param_h = cpu_to_le32(upper_32_bits(in_param)); + mb->cmd = attr->op; + mb->tag = attr->tag; + mb->mbx_ue_id = attr->mbx_ue_id; +} + +static int ubase_cmd_mbox_poll(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + struct ubase_cmdq_desc desc; + union ubase_mbox *mb; + u8 status = 0; + int ret; + + if (udev->reset_stage == UBASE_RESET_STAGE_UNINIT) + return 0; + + mb = (union ubase_mbox *)desc.data; + + ubase_cmd_setup_basic_desc(&desc, UBASE_OPC_POST_MB, false, 1); + + ubase_mbox_desc_init(udev, mb, (u64)mailbox->dma, attr); + + mb->seq_num = CMD_MBX_POLL_VALUE; + + ret = ubase_post_mailbox(udev, &desc, UBASE_MBX_TX_TIMEOUT, &status, + false); + if (ret) { + ubase_err(udev, + "failed to post mailbox 0x%x in poll mode, status = %u, ret = %d.\n", + attr->op, status, ret); + return ret; + } + + return ubase_poll_mbox_done(udev, UBASE_MBX_TX_TIMEOUT); +} + +static int ubase_cmd_mbox_event(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + union ubase_mbox mb_out = {0}; + union ubase_mbox mb_in = {0}; + struct ubase_cmd_buf in, out; + int ret; + + if (udev->reset_stage == UBASE_RESET_STAGE_UNINIT) + return 0; + + ubase_mbox_desc_init(udev, &mb_in, (u64)mailbox->dma, attr); + + mb_in.event_en = 1; + __ubase_fill_inout_buf(&in, UBASE_OPC_POST_MB, false, + sizeof(union ubase_mbox), (void *)&mb_in); + __ubase_fill_inout_buf(&out, UBASE_OPC_POST_MB, false, + sizeof(union ubase_mbox), (void *)&mb_out); + + ret = ubase_post_mailbox_by_event(udev, &in, &out); + if (ret) + ubase_err(udev, + "failed to post mailbox 0x%x in event mode, ret = %d.\n", + attr->op, ret); + + return ret; +} + +int ubase_create_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + struct ubase_ctx_page **ctx_page, u32 npage) +{ + int ret; + + *ctx_page = kzalloc(sizeof(struct ubase_ctx_page), GFP_KERNEL); + if (!(*ctx_page)) + return -ENOMEM; + + (*ctx_page)->iova = ctx_buf->dma_ctx_buf_ba + npage * PAGE_SIZE; + refcount_set(&(*ctx_page)->refcount, 1); + (*ctx_page)->npage = npage; + ret = ummu_fill_pages(ctx_buf->slot, (*ctx_page)->iova, + UBASE_IOVA_COMM_PFN_CNT); + if (ret) { + ubase_err(udev, "failed to fill pages in ummu, ret = %d\n", ret); + kfree(*ctx_page); + *ctx_page = NULL; + } + + return ret; +} + +void ubase_destroy_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_page *ctx_page, + struct ubase_ctx_buf_cap *ctx_buf) +{ + int ret; + + ret = ummu_drain_pages(ctx_buf->slot, ctx_page->iova, + UBASE_IOVA_COMM_PFN_CNT); + if (ret) + ubase_err(udev, + "failed to drain pages in ummu, npage = %u, ret = %d.\n", + ctx_page->npage, ret); + + kfree(ctx_page); +} + +static int ubase_use_buf_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, u32 tag) +{ + u32 cnt_per_page_shift = ctx_buf->cnt_per_page_shift; + u32 npage = tag >> cnt_per_page_shift; + struct ubase_ctx_page *ctx_page; + int ret; + + mutex_lock(&ctx_buf->ctx_mutex); + + ctx_page = (struct ubase_ctx_page *)xa_load(&ctx_buf->ctx_xa, npage); + if (!ctx_page) { + ret = ubase_create_ctx_page(udev, ctx_buf, &ctx_page, npage); + if (ret) { + ubase_err(udev, "failed to create context page, ret = %d.\n", + ret); + goto err_create; + } + + ret = xa_err(xa_store(&ctx_buf->ctx_xa, npage, ctx_page, + GFP_KERNEL)); + if (ret) { + ubase_err(udev, "failed to store page, ret = %d.\n", + ret); + goto err_store; + } + } + + refcount_inc(&ctx_page->refcount); + mutex_unlock(&ctx_buf->ctx_mutex); + + return 0; +err_store: + ubase_destroy_ctx_page(udev, ctx_page, ctx_buf); +err_create: + mutex_unlock(&ctx_buf->ctx_mutex); + + return ret; +} + +static void ubase_free_buf_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, u32 tag) +{ + struct ubase_ctx_page *ctx_page; + u32 cnt_per_page_shift; + u32 npage; + + if (!ctx_buf) + return; + + cnt_per_page_shift = ctx_buf->cnt_per_page_shift; + npage = tag >> cnt_per_page_shift; + + mutex_lock(&ctx_buf->ctx_mutex); + + ctx_page = (struct ubase_ctx_page *)xa_load(&ctx_buf->ctx_xa, npage); + if (!ctx_page) { + ubase_err(udev, + "no find ctx page in free buf page, npage = %u.\n", + npage); + mutex_unlock(&ctx_buf->ctx_mutex); + return; + } + + refcount_dec(&ctx_page->refcount); + if (refcount_dec_if_one(&ctx_page->refcount)) { + ubase_info(udev, + "refcout of ctx page is equal to one and the ctx_page is going to erased.\n"); + xa_erase(&ctx_buf->ctx_xa, npage); + ubase_destroy_ctx_page(udev, ctx_page, ctx_buf); + } + + mutex_unlock(&ctx_buf->ctx_mutex); +} + +static struct ubase_ctx_buf_cap* +ubase_parse_common_buf(struct ubase_mbx_attr *attr, + struct mbx_op_match op_matches[], + enum ubase_mb_type *type, u32 size) +{ + u32 i; + + for (i = 0; i < size; i++) { + if (attr->op == op_matches[i].op) { + *type = op_matches[i].type; + return op_matches[i].ctx_caps; + } + } + + return NULL; +} + +static bool ubase_is_jfs_opcode(u8 op) +{ + switch (op) { + case UBASE_MB_CREATE_JFS_CONTEXT: + case UBASE_MB_MODIFY_JFS_CONTEXT: + case UBASE_MB_DESTROY_JFS_CONTEXT: + case UBASE_MB_QUERY_JFS_CONTEXT: + return true; + default: + return false; + } +} + +static struct ubase_ctx_buf_cap* +ubase_parse_ta_opcode_buf(struct ubase_dev *udev, struct ubase_mbx_attr *attr, + enum ubase_mb_type *type) +{ + struct mbx_op_match ta_matches[] = { + {UBASE_MB_CREATE_JFS_CONTEXT, UBASE_MB_CREATE, &udev->ctx_buf.jfs}, + {UBASE_MB_MODIFY_JFS_CONTEXT, UBASE_MB_MODIFY, &udev->ctx_buf.jfs}, + {UBASE_MB_DESTROY_JFS_CONTEXT, UBASE_MB_DESTROY, &udev->ctx_buf.jfs}, + {UBASE_MB_QUERY_JFS_CONTEXT, UBASE_MB_QUERY, &udev->ctx_buf.jfs}, + {UBASE_MB_CREATE_JFC_CONTEXT, UBASE_MB_CREATE, &udev->ctx_buf.jfc}, + {UBASE_MB_MODIFY_JFC_CONTEXT, UBASE_MB_MODIFY, &udev->ctx_buf.jfc}, + {UBASE_MB_DESTROY_JFC_CONTEXT, UBASE_MB_DESTROY, &udev->ctx_buf.jfc}, + {UBASE_MB_QUERY_JFC_CONTEXT, UBASE_MB_QUERY, &udev->ctx_buf.jfc}, + {UBASE_MB_CREATE_JFR_CONTEXT, UBASE_MB_CREATE, &udev->ctx_buf.jfr}, + {UBASE_MB_MODIFY_JFR_CONTEXT, UBASE_MB_MODIFY, &udev->ctx_buf.jfr}, + {UBASE_MB_DESTROY_JFR_CONTEXT, UBASE_MB_DESTROY, &udev->ctx_buf.jfr}, + {UBASE_MB_QUERY_JFR_CONTEXT, UBASE_MB_QUERY, &udev->ctx_buf.jfr}, + {UBASE_MB_CREATE_JETTY_GROUP_CONTEXT, UBASE_MB_CREATE, &udev->ctx_buf.jtg}, + {UBASE_MB_MODIFY_JETTY_GROUP_CONTEXT, UBASE_MB_MODIFY, &udev->ctx_buf.jtg}, + {UBASE_MB_DESTROY_JETTY_GROUP_CONTEXT, UBASE_MB_DESTROY, &udev->ctx_buf.jtg}, + {UBASE_MB_QUERY_JETTY_GROUP_CONTEXT, UBASE_MB_QUERY, &udev->ctx_buf.jtg}, + {UBASE_MB_CREATE_RC_CONTEXT, UBASE_MB_CREATE, &udev->ctx_buf.rc}, + {UBASE_MB_MODIFY_RC_CONTEXT, UBASE_MB_MODIFY, &udev->ctx_buf.rc}, + {UBASE_MB_DESTROY_RC_CONTEXT, UBASE_MB_DESTROY, &udev->ctx_buf.rc}, + {UBASE_MB_QUERY_RC_CONTEXT, UBASE_MB_QUERY, &udev->ctx_buf.rc}, + }; + u32 size = ARRAY_SIZE(ta_matches); + + return ubase_parse_common_buf(attr, ta_matches, type, size); +} + +static struct ubase_ctx_buf_cap* +ubase_parse_opcode_buf(struct ubase_dev *udev, struct ubase_mbx_attr *attr, + enum ubase_mb_type *type) +{ + if (ubase_is_jfs_opcode(attr->op) && + test_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits)) + return NULL; + + return ubase_parse_ta_opcode_buf(udev, attr, type); +} + +static int ubase_check_buf_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, u32 tag) +{ + u32 cnt_per_page_shift = ctx_buf->cnt_per_page_shift; + u32 npage = tag >> cnt_per_page_shift; + struct ubase_ctx_page *ctx_page; + + mutex_lock(&ctx_buf->ctx_mutex); + + ctx_page = (struct ubase_ctx_page *)xa_load(&ctx_buf->ctx_xa, npage); + if (!ctx_page) { + ubase_err(udev, + "failed to find ctx page in free buf page, npage = %u.\n", + npage); + mutex_unlock(&ctx_buf->ctx_mutex); + return -EINVAL; + } + + mutex_unlock(&ctx_buf->ctx_mutex); + + return 0; +} + +static int ubase_hw_upgrade_ctx_event(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + int ret; + + down(&udev->mb_cmd.sem); + ret = ubase_cmd_mbox_event(udev, attr, mailbox); + up(&udev->mb_cmd.sem); + + return ret; +} + +int ubase_hw_upgrade_ctx_poll(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + int ret; + + down(&udev->mb_cmd.sem); + ret = ubase_cmd_mbox_poll(udev, attr, mailbox); + up(&udev->mb_cmd.sem); + + return ret; +} + +int __ubase_hw_upgrade_ctx(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + return ubase_hw_upgrade_ctx_event(udev, attr, mailbox); +} + +int __ubase_hw_upgrade_ctx_ex(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + enum ubase_mb_type type = UBASE_MB_OTHER; + struct ubase_ctx_buf_cap *ctx_buf; + int ret; + + ctx_buf = ubase_parse_opcode_buf(udev, attr, &type); + if (ctx_buf) { + if (type == UBASE_MB_CREATE) { + ret = ubase_use_buf_ctx_page(udev, ctx_buf, attr->tag); + if (ret) { + ubase_err(udev, "failed to write context va, ret = %d.\n", + ret); + return ret; + } + } else if (type == UBASE_MB_QUERY) { + ret = ubase_check_buf_ctx_page(udev, ctx_buf, attr->tag); + if (ret) { + ubase_info(udev, + "A query operation is performed before the create operation, 0 is returned by default, op = 0x%x.\n", + attr->op); + return 0; + } + } else if (type == UBASE_MB_MODIFY || type == UBASE_MB_DESTROY) { + ret = ubase_check_buf_ctx_page(udev, ctx_buf, attr->tag); + if (ret) { + ubase_info(udev, + "An access operation is performed before the create operation, op = 0x%x.\n", + attr->op); + return ret; + } + } + } + + ret = __ubase_hw_upgrade_ctx(udev, attr, mailbox); + if (ret && type == UBASE_MB_CREATE) + ubase_free_buf_ctx_page(udev, ctx_buf, attr->tag); + + return ret; +} + +int ubase_hw_upgrade_ctx_ex(struct auxiliary_device *aux_dev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox) +{ + struct ubase_dev *udev; + + if (!aux_dev || !attr || !mailbox) + return -EINVAL; + + udev = ubase_get_udev_by_adev(aux_dev); + if (!test_bit(UBASE_STATE_CTX_READY_B, &udev->state_bits)) + return -EAGAIN; + + return __ubase_hw_upgrade_ctx_ex(udev, attr, mailbox); +} +EXPORT_SYMBOL(ubase_hw_upgrade_ctx_ex); diff --git a/drivers/ub/ubase/ubase_mailbox.h b/drivers/ub/ubase/ubase_mailbox.h new file mode 100644 index 0000000000000000000000000000000000000000..33e72b7d15b385b21ee38d897ad203469079f44d --- /dev/null +++ b/drivers/ub/ubase/ubase_mailbox.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_MAILBOX_H__ +#define __UBASE_MAILBOX_H__ + +#include + +#include "ubase_dev.h" + +#define CMD_MBX_POLL_VALUE 0xffff +#define UBASE_MAILBOX_SIZE 4096 +#define UBASE_MBX_TX_TIMEOUT 30000 + +enum ubase_mb_type { + UBASE_MB_CREATE, + UBASE_MB_MODIFY, + UBASE_MB_DESTROY, + UBASE_MB_QUERY, + UBASE_MB_OTHER, +}; + +struct mbx_op_match { + u32 op; + enum ubase_mb_type type; + struct ubase_ctx_buf_cap *ctx_caps; +}; + +int ubase_mbox_cmd_init(struct ubase_dev *udev); +void ubase_mbox_cmd_uninit(struct ubase_dev *udev); +struct ubase_cmd_mailbox *__ubase_alloc_cmd_mailbox(struct ubase_dev *udev); +void __ubase_free_cmd_mailbox(struct ubase_dev *udev, + struct ubase_cmd_mailbox *mailbox); +void ubase_destroy_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_page *ctx_page, + struct ubase_ctx_buf_cap *ctx_buf); +int ubase_hw_upgrade_ctx_poll(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox); +int __ubase_hw_upgrade_ctx(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox); +int __ubase_hw_upgrade_ctx_ex(struct ubase_dev *udev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox); +int ubase_create_ctx_page(struct ubase_dev *udev, + struct ubase_ctx_buf_cap *ctx_buf, + struct ubase_ctx_page **ctx_page, u32 npage); + +#endif diff --git a/drivers/ub/ubase/ubase_main.c b/drivers/ub/ubase/ubase_main.c new file mode 100644 index 0000000000000000000000000000000000000000..2c97a842c3dda44a0e45e334724f9f4d95482823 --- /dev/null +++ b/drivers/ub/ubase/ubase_main.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "debugfs/ubase_debugfs.h" + +static int __init ubase_init(void) +{ + int ret; + + ret = ubase_dbg_register_debugfs(); + if (ret) + return ret; + + ret = ubase_ubus_register_driver(); + if (ret) + ubase_dbg_unregister_debugfs(); + + return ret; +} + +static void __exit ubase_exit(void) +{ + ubase_ubus_unregister_driver(); + ubase_dbg_unregister_debugfs(); +} + +module_init(ubase_init); +module_exit(ubase_exit); + +MODULE_DESCRIPTION("UBASE: Hisilicon Network Driver"); +MODULE_IMPORT_NS(UB_UBFI); +MODULE_IMPORT_NS(UB_UBUS); +MODULE_LICENSE("GPL"); +MODULE_VERSION(UBASE_MOD_VERSION); diff --git a/drivers/ub/ubase/ubase_pmem.c b/drivers/ub/ubase/ubase_pmem.c new file mode 100644 index 0000000000000000000000000000000000000000..a43f1c39c0be72afff58d361e8040c1e8403befc --- /dev/null +++ b/drivers/ub/ubase/ubase_pmem.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include + +#include "ubase_pmem.h" + +struct ubase_pmem_init_func { + int (*init)(struct ubase_dev *udev); + void (*uninit)(struct ubase_dev *udev); +}; + +static u16 ubase_calc_comm_page_cnt(struct ubase_dev *udev) +{ + u32 ta_timer_size = ubase_ta_timer_align_size(udev); + u32 jfs_ctx_size = ubase_jfs_ctx_align_size(udev); + + return DIV_ROUND_UP(jfs_ctx_size + ta_timer_size, UBASE_PMEM_PAGE_SIZE); +} + +static inline u16 ubase_calc_que_page_cnt(u32 que_num, u64 que_size) +{ + if (!que_size) + return 0; + + return que_size > UBASE_PMEM_PAGE_SIZE ? + DIV_ROUND_UP(que_size * que_num, UBASE_PMEM_PAGE_SIZE) : + DIV_ROUND_UP(que_num, UBASE_PMEM_PAGE_SIZE / que_size); +} + +static u16 ubase_calc_udma_page_cnt(struct ubase_dev *udev) +{ +#define RCE_SIZE 64 + + struct ubase_adev_caps *udma_caps = &udev->caps.udma_caps; + u32 rcq_size; + + rcq_size = ALIGN(udma_caps->rc_que_depth * RCE_SIZE, PAGE_SIZE); + + return ubase_calc_que_page_cnt(udma_caps->rc_max_cnt, rcq_size); +} + +static int ubase_init_pmem_ctx(struct ubase_dev *udev, const char *type, + struct ubase_pmem_ctx *ctx, u16 page_cnt) +{ + if (!page_cnt) + return 0; + + ctx->pgs = kcalloc(page_cnt, sizeof(struct page *), GFP_KERNEL); + if (!ctx->pgs) { + ubase_err(udev, "failed to alloc mem for %s pgs.page_cnt=%u\n", + type, page_cnt); + return -ENOMEM; + } + + ctx->sg = kzalloc(page_cnt * sizeof(struct scatterlist), GFP_KERNEL); + if (!ctx->sg) { + ubase_err(udev, "failed to alloc mem for %s sg.page_cnt=%u\n", + type, page_cnt); + goto free_pgs; + } + + ctx->page_cnt = page_cnt; + return 0; + +free_pgs: + kfree(ctx->pgs); + return -ENOMEM; +} + +static int ubase_init_comm_pmem_ctx(struct ubase_dev *udev) +{ + return ubase_init_pmem_ctx(udev, "comm", &udev->pmem_info.comm, + ubase_calc_comm_page_cnt(udev)); +} + +static int ubase_init_udma_pmem_ctx(struct ubase_dev *udev) +{ + return ubase_init_pmem_ctx(udev, "udma", &udev->pmem_info.udma, + ubase_calc_udma_page_cnt(udev)); +} + +static void ubase_uninit_pmem_ctx(struct ubase_pmem_ctx *ctx) +{ + if (!ctx->page_cnt) + return; + + kfree(ctx->pgs); + ctx->pgs = NULL; + + kfree(ctx->sg); + ctx->sg = NULL; + + ctx->page_cnt = 0; +} + +static void ubase_uninit_comm_pmem_ctx(struct ubase_dev *udev) +{ + ubase_uninit_pmem_ctx(&udev->pmem_info.comm); +} + +static void ubase_uninit_udma_pmem_ctx(struct ubase_dev *udev) +{ + ubase_uninit_pmem_ctx(&udev->pmem_info.udma); +} + +static int ubase_alloc_pmem(struct ubase_dev *udev, const char *type, + struct page **pgs, int page_cnt) +{ + int order = get_order(UBASE_PMEM_PAGE_SIZE); + int i; + + for (i = 0; i < page_cnt; i++) { + pgs[i] = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); + if (!pgs[i]) { + ubase_err(udev, + "failed to alloc %s pages[%d], page_cnt = %d, order = %d.\n", + type, i, page_cnt, order); + goto free_pgs; + } + } + + return 0; + +free_pgs: + for (i -= 1; i >= 0; i--) + __free_pages(pgs[i], order); + + return -ENOMEM; +} + +static int ubase_alloc_comm_pmem(struct ubase_dev *udev) +{ + struct ubase_pmem_ctx *ctx = &udev->pmem_info.comm; + + return ubase_alloc_pmem(udev, "comm", ctx->pgs, ctx->page_cnt); +} + +static int ubase_alloc_udma_pmem(struct ubase_dev *udev) +{ + struct ubase_pmem_ctx *ctx = &udev->pmem_info.udma; + + return ubase_alloc_pmem(udev, "udma", ctx->pgs, ctx->page_cnt); +} + +static void ubase_free_pmem(struct ubase_pmem_ctx *ctx) +{ + int order = get_order(UBASE_PMEM_PAGE_SIZE); + u32 i; + + for (i = 0; i < ctx->page_cnt; i++) + __free_pages(ctx->pgs[i], order); +} + +static void ubase_free_comm_pmem(struct ubase_dev *udev) +{ + ubase_free_pmem(&udev->pmem_info.comm); +} + +static void ubase_free_udma_pmem(struct ubase_dev *udev) +{ + ubase_free_pmem(&udev->pmem_info.udma); +} + +static int ubase_map_pmem(struct ubase_dev *udev, const char *type, + struct ubase_pmem_ctx *ctx) +{ + struct scatterlist *iter; + unsigned int nents; + u32 i; + + if (!ctx->page_cnt) + return 0; + + sg_init_table(ctx->sg, ctx->page_cnt); + + for_each_sg(ctx->sg, iter, ctx->page_cnt, i) + sg_set_page(iter, ctx->pgs[i], UBASE_PMEM_PAGE_SIZE, 0); + + nents = dma_map_sg(udev->dev, ctx->sg, ctx->page_cnt, DMA_BIDIRECTIONAL); + if (!nents) { + ubase_err(udev, "failed to map %s pages, page_cnt = %u.\n", + type, ctx->page_cnt); + return -ENOMEM; + } + + ctx->dma_addr = sg_dma_address(&ctx->sg[0]); + return 0; +} + +static int ubase_map_comm_pmem(struct ubase_dev *udev) +{ + return ubase_map_pmem(udev, "comm", &udev->pmem_info.comm); +} + +static int ubase_map_udma_pmem(struct ubase_dev *udev) +{ + struct ubase_pmem_caps *pmem = &udev->caps.udma_caps.pmem; + int ret; + + ret = ubase_map_pmem(udev, "udma", &udev->pmem_info.udma); + if (ret) + return ret; + + pmem->dma_addr = udev->pmem_info.udma.dma_addr; + pmem->dma_len = udev->pmem_info.udma.page_cnt * UBASE_PMEM_PAGE_SIZE; + + return 0; +} + +static void ubase_unmap_pmem(struct ubase_dev *udev, struct ubase_pmem_ctx *ctx) +{ + if (!ctx->page_cnt) + return; + + dma_unmap_sg(udev->dev, ctx->sg, ctx->page_cnt, DMA_BIDIRECTIONAL); + + ctx->dma_addr = 0; +} + +static void ubase_unmap_comm_pmem(struct ubase_dev *udev) +{ + ubase_unmap_pmem(udev, &udev->pmem_info.comm); +} + +static void ubase_unmap_udma_pmem(struct ubase_dev *udev) +{ + struct ubase_pmem_caps *pmem = &udev->caps.udma_caps.pmem; + + ubase_unmap_pmem(udev, &udev->pmem_info.udma); + + pmem->dma_addr = 0; + pmem->dma_len = 0; +} + +static struct ubase_pmem_init_func ubase_pmem_init_map[] = { + { + .init = ubase_init_comm_pmem_ctx, + .uninit = ubase_uninit_comm_pmem_ctx, + }, + { + .init = ubase_init_udma_pmem_ctx, + .uninit = ubase_uninit_udma_pmem_ctx, + }, + { + .init = ubase_alloc_comm_pmem, + .uninit = ubase_free_comm_pmem, + }, + { + .init = ubase_alloc_udma_pmem, + .uninit = ubase_free_udma_pmem, + }, + { + .init = ubase_map_comm_pmem, + .uninit = ubase_unmap_comm_pmem, + }, + { + .init = ubase_map_udma_pmem, + .uninit = ubase_unmap_udma_pmem, + }, +}; + +static int __ubase_prealloc_mem_init(struct ubase_dev *udev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ubase_pmem_init_map); i++) { + if (ubase_pmem_init_map[i].init(udev)) + goto init_fail; + } + + return 0; + +init_fail: + for (i -= 1; i >= 0; i--) + ubase_pmem_init_map[i].uninit(udev); + + return -ENOMEM; +} + +static void ubase_reset_pmem(struct ubase_pmem_ctx *ctx) +{ + void *va; + u32 i; + + for (i = 0; i < ctx->page_cnt; i++) { + va = page_address(ctx->pgs[i]); + memset(va, 0, UBASE_PMEM_PAGE_SIZE); + } +} + +static void ubase_prealloc_mem_reinit(struct ubase_dev *udev) +{ + ubase_reset_pmem(&udev->pmem_info.comm); + ubase_reset_pmem(&udev->pmem_info.udma); +} + +int ubase_prealloc_mem_init(struct ubase_dev *udev) +{ + if (test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + ubase_prealloc_mem_reinit(udev); + return 0; + } + + if (!ubase_dev_prealloc_supported(udev)) + return 0; + + ubase_info(udev, "pre-alloc memory feature is enabled.\n"); + + if (__ubase_prealloc_mem_init(udev)) { + ubase_warn(udev, + "warning: pre-alloc pages failed, will use normal pages.\n"); + return 0; /* use normal pages, must return success. */ + } + + set_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits); + + return 0; +} + +void ubase_prealloc_mem_uninit(struct ubase_dev *udev) +{ + int i; + + if (test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) + return; + + if (!test_and_clear_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits)) + return; + + for (i = ARRAY_SIZE(ubase_pmem_init_map) - 1; i >= 0; i--) + ubase_pmem_init_map[i].uninit(udev); +} + +bool ubase_adev_prealloc_supported(struct auxiliary_device *adev) +{ + struct ubase_dev *udev; + + if (!adev) + return false; + + udev = __ubase_get_udev_by_adev(adev); + + return test_bit(UBASE_STATE_PREALLOC_OK_B, &udev->state_bits); +} +EXPORT_SYMBOL(ubase_adev_prealloc_supported); diff --git a/drivers/ub/ubase/ubase_pmem.h b/drivers/ub/ubase/ubase_pmem.h new file mode 100644 index 0000000000000000000000000000000000000000..dc527c666346bb66b77ab4dfc2e565dbac2a5dd8 --- /dev/null +++ b/drivers/ub/ubase/ubase_pmem.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_PMEM_H__ +#define __UBASE_PMEM_H__ + +#include "ubase_dev.h" + +int ubase_prealloc_mem_init(struct ubase_dev *udev); +void ubase_prealloc_mem_uninit(struct ubase_dev *udev); + +#endif /* _UBASE_PMEM_H */ diff --git a/drivers/ub/ubase/ubase_qos_hw.c b/drivers/ub/ubase/ubase_qos_hw.c new file mode 100644 index 0000000000000000000000000000000000000000..5a5881f79547fe5ffa7a88a790ca6c4d030a7da7 --- /dev/null +++ b/drivers/ub/ubase/ubase_qos_hw.c @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include + +#include "ubase_cmd.h" +#include "ubase_ctrlq.h" +#include "ubase_hw.h" + +int ubase_query_sl_vl_map(struct ubase_dev *udev, u8 *sl_vl) +{ + struct ubase_config_sl_vl_cmd resp = {0}; + struct ubase_config_sl_vl_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.sl_num = UBASE_MAX_SL_NUM; + __ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_TA_SL_VL_MAP, true, + sizeof(req), &req); + __ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_TA_SL_VL_MAP, false, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, "failed to query sl_vl map, ret = %d.\n", ret); + return ret; + } + + memcpy(sl_vl, resp.sl_vl, UBASE_MAX_SL_NUM); + return 0; +} + +static inline unsigned long ubase_convert_sl_vl_bitmap(struct ubase_dev *udev, + unsigned long sl_bitmap) +{ + unsigned long vl_bitmap = 0; + u8 i; + + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (!test_bit(i, &sl_bitmap)) + continue; + + vl_bitmap |= 1 << udev->qos.ue_sl_vl[i]; + } + + return vl_bitmap; +} + +static void ubase_get_vl_sche_info(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos, + unsigned long *vl_bitmap, + u8 *vl_bw, u8 *vl_tsa) +{ + unsigned long sl_bitmap = sl_priqos->sl_bitmap; + u8 i; + + *vl_bitmap = ubase_convert_sl_vl_bitmap(udev, sl_bitmap); + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (!test_bit(i, &sl_bitmap)) + continue; + + vl_bw[udev->qos.ue_sl_vl[i]] = sl_priqos->weight[i]; + vl_tsa[udev->qos.ue_sl_vl[i]] = sl_priqos->sch_mode[i]; + } +} + +static void ubase_prase_tm_vl_sch_resp(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos, + struct ubase_cfg_tm_vl_sch_cmd *resp) +{ + unsigned long tsa_bitmap = le16_to_cpu(resp->vl_tsa); + unsigned long sl_bitmap = sl_priqos->sl_bitmap; + u8 i; + + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (!test_bit(i, &sl_bitmap)) + continue; + + sl_priqos->weight[i] = resp->vl_bw[udev->qos.ue_sl_vl[i]]; + sl_priqos->sch_mode[i] = test_bit(udev->qos.ue_sl_vl[i], + &tsa_bitmap) ? + UBASE_SL_DWRR : UBASE_SL_SP; + } +} + +static void ubase_prase_ets_vl_sch_resp(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos, + struct ubase_cfg_ets_vl_sch_cmd *resp) +{ + unsigned long sl_bitmap = sl_priqos->sl_bitmap; + u8 i; + + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (!test_bit(i, &sl_bitmap)) + continue; + + sl_priqos->weight[i] = resp->vl_bw[udev->qos.ue_sl_vl[i]]; + sl_priqos->sch_mode[i] = sl_priqos->weight[i] ? + UBASE_SL_DWRR : UBASE_SL_SP; + } +} + +static int ubase_check_sp_sch_param(struct ubase_dev *udev, u8 vl_bw, + u32 *bw_sum, u8 vl_idx, bool is_ets) +{ + if (is_ets) { + if (vl_bw) { + ubase_err(udev, + "vl(%u) vl_bw must be 0 in ets sp mode.\n", + vl_idx); + return -EINVAL; + } + + return 0; + } + + if (!vl_bw) { + ubase_err(udev, "vl(%u) vl_bw cannot be 0 in tm sp mode.\n", + vl_idx); + return -EINVAL; + } + + *bw_sum += vl_bw; + + return 0; +} + +static int ubase_check_dwrr_sch_param(struct ubase_dev *udev, u8 vl_bw, + u32 *bw_sum, u8 vl_idx) +{ + if (!vl_bw) { + ubase_err(udev, "vl(%u) bw cannot be 0 in dwrr mode.\n", + vl_idx); + return -EINVAL; + } + + *bw_sum += vl_bw; + + return 0; +} + +static int __ubase_check_qos_sch_param(struct ubase_dev *udev, + unsigned long vl_bitmap, + u8 *vl_bw, u8 *vl_tsa, bool is_ets) +{ +#define UBASE_BW_PERCENT 100 + + u32 bw_sum = 0; + int ret; + u8 i; + + for (i = 0; i < UBASE_MAX_VL_NUM; i++) { + if (!test_bit(i, &vl_bitmap)) + continue; + + switch (vl_tsa[i]) { + case IEEE_8021QAZ_TSA_STRICT: + ret = ubase_check_sp_sch_param(udev, vl_bw[i], &bw_sum, + i, is_ets); + if (ret) + return ret; + break; + case IEEE_8021QAZ_TSA_ETS: + ret = ubase_check_dwrr_sch_param(udev, vl_bw[i], + &bw_sum, i); + if (ret) + return ret; + break; + default: + ubase_err(udev, "not support tc%u tsa model: %u\n", + i, vl_tsa[i]); + return -EINVAL; + } + } + + if (bw_sum && bw_sum != UBASE_BW_PERCENT) { + ubase_err(udev, + "the vl_bw sum does not add up to 100 in %s mode.\n", + is_ets ? "ets dwrr" : "tm sp/dwrr"); + return -EINVAL; + } + + return 0; +} + +static int __ubase_config_tm_vl_sch(struct ubase_dev *udev, u16 vl_bitmap, + u8 *vl_bw, u8 *vl_tsa) +{ + struct ubase_cfg_tm_vl_sch_cmd req = {0}; + struct ubase_cmd_buf in; + u16 tsa_bitmap = 0; + int ret; + u8 i; + + /* the configuration takes effect for all entities. */ + req.bus_ue_id = cpu_to_le16(USHRT_MAX); + req.vl_bitmap = cpu_to_le16(vl_bitmap); + + for (i = 0; i < UBASE_MAX_VL_NUM; i++) + tsa_bitmap |= vl_tsa[i] ? 1 << i : 0; + + req.vl_tsa = cpu_to_le16(tsa_bitmap); + memcpy(req.vl_bw, vl_bw, UBASE_MAX_VL_NUM); + + ubase_fill_inout_buf(&in, UBASE_OPC_TA_VL_SCH_CONFIG, false, + sizeof(req), &req); + + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, "failed to config tm vl sch, ret = %d", ret); + + return ret; +} + +static int __ubase_config_ets_vl_sch(struct ubase_dev *udev, u16 vl_bitmap, + u8 *vl_bw, u32 port_bitmap) +{ + struct ubase_cfg_ets_vl_sch_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + req.port_bitmap = cpu_to_le32(port_bitmap); + req.vl_bitmap = cpu_to_le16(vl_bitmap); + memcpy(req.vl_bw, vl_bw, UBASE_MAX_VL_NUM); + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_ETS_TC_INFO, false, sizeof(req), + &req); + + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, "failed to cfg ets vl sch, ret = %d.", ret); + + return ret; +} + +static int ubase_set_tm_priqos(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos) +{ + u8 vl_tsa[UBASE_MAX_VL_NUM] = {0}; + u8 vl_bw[UBASE_MAX_VL_NUM] = {0}; + unsigned long vl_bitmap = 0; + int ret; + + ubase_get_vl_sche_info(udev, sl_priqos, &vl_bitmap, vl_bw, vl_tsa); + + ret = __ubase_check_qos_sch_param(udev, vl_bitmap, vl_bw, vl_tsa, false); + if (ret) + return ret; + + return __ubase_config_tm_vl_sch(udev, vl_bitmap, vl_bw, vl_tsa); +} + +static int ubase_get_tm_priqos(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos) +{ + struct ubase_cfg_tm_vl_sch_cmd resp = {0}; + struct ubase_cfg_tm_vl_sch_cmd req = {0}; + struct ubase_cmd_buf in, out; + u16 vl_bitmap; + int ret; + + vl_bitmap = ubase_convert_sl_vl_bitmap(udev, sl_priqos->sl_bitmap); + req.vl_bitmap = cpu_to_le16(vl_bitmap); + + ubase_fill_inout_buf(&in, UBASE_OPC_TA_VL_SCH_CONFIG, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_TA_VL_SCH_CONFIG, false, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to get tm vl sch mode and weight, ret = %d.\n", + ret); + return ret; + } + + ubase_prase_tm_vl_sch_resp(udev, sl_priqos, &resp); + + return 0; +} + +static int ubase_set_ets_priqos(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos) +{ + u8 vl_tsa[UBASE_MAX_VL_NUM] = {0}; + u8 vl_bw[UBASE_MAX_VL_NUM] = {0}; + unsigned long vl_bitmap = 0; + int ret; + + ubase_get_vl_sche_info(udev, sl_priqos, &vl_bitmap, vl_bw, vl_tsa); + + ret = __ubase_check_qos_sch_param(udev, vl_bitmap, vl_bw, vl_tsa, true); + if (ret) + return ret; + + return __ubase_config_ets_vl_sch(udev, vl_bitmap, vl_bw, + sl_priqos->port_bitmap); +} + +int ubase_query_ets_tc(struct ubase_dev *udev, u32 port_bitmap, + u16 vl_bitmap, struct ubase_cfg_ets_vl_sch_cmd *resp) +{ + struct ubase_cfg_ets_vl_sch_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.port_bitmap = cpu_to_le32(port_bitmap); + req.vl_bitmap = cpu_to_le16(vl_bitmap); + + ubase_fill_inout_buf(&in, UBASE_OPC_CFG_ETS_TC_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_CFG_ETS_TC_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) + ubase_err(udev, + "failed to query ets tc info, ret = %d.\n", ret); + + return ret; +} + +static int ubase_get_ets_priqos(struct ubase_dev *udev, + struct ubase_sl_priqos *sl_priqos) +{ + struct ubase_cfg_ets_vl_sch_cmd resp = {0}; + u32 port_bitmap; + u16 vl_bitmap; + int ret; + + vl_bitmap = ubase_convert_sl_vl_bitmap(udev, sl_priqos->sl_bitmap); + port_bitmap = sl_priqos->port_bitmap; + + ret = ubase_query_ets_tc(udev, port_bitmap, vl_bitmap, &resp); + if (ret) + return ret; + + ubase_prase_ets_vl_sch_resp(udev, sl_priqos, &resp); + + return 0; +} + +int ubase_query_ets_tcg(struct ubase_dev *udev, + struct ubase_query_ets_tcg_cmd *resp) +{ + struct ubase_query_ets_tcg_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_ETS_TCG_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_ETS_TCG_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) + ubase_err(udev, + "failed to query ets tcg info, ret = %d.\n", ret); + + return ret; +} + +int ubase_query_ets_port(struct ubase_dev *udev, + struct ubase_query_ets_port_cmd *resp) +{ + struct ubase_query_ets_port_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_ETS_PORT_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_ETS_PORT_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) + ubase_err(udev, + "failed to query ets port info, ret = %d.\n", ret); + + return ret; +} + +int ubase_query_fst_fvt_rqmt(struct ubase_dev *udev, + struct ubase_query_fst_fvt_rqmt_cmd *resp, + u16 bus_ue_id) +{ + struct ubase_query_fst_fvt_rqmt_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.bus_ue_id = cpu_to_le16(bus_ue_id); + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_FST_FVT_RQMT, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_FST_FVT_RQMT, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret == -EPERM) + return -EOPNOTSUPP; + if (ret) + ubase_err(udev, + "failed to query fst fvt rqmt info, ret=%d.\n", ret); + + return ret; +} + +int ubase_check_qos_sch_param(struct auxiliary_device *adev, u16 vl_bitmap, + u8 *vl_bw, u8 *vl_tsa, bool is_ets) +{ + struct ubase_dev *udev; + + if (!adev || !vl_tsa || !vl_bw) + return -EINVAL; + + udev = __ubase_get_udev_by_adev(adev); + + return __ubase_check_qos_sch_param(udev, vl_bitmap, vl_bw, vl_tsa, + is_ets); +} +EXPORT_SYMBOL(ubase_check_qos_sch_param); + +int ubase_config_tm_vl_sch(struct auxiliary_device *adev, u16 vl_bitmap, + u8 *vl_bw, u8 *vl_tsa) +{ + struct ubase_dev *udev; + + if (!adev || !vl_bw || !vl_tsa) + return -EINVAL; + + udev = __ubase_get_udev_by_adev(adev); + + return __ubase_config_tm_vl_sch(udev, vl_bitmap, vl_bw, vl_tsa); +} +EXPORT_SYMBOL(ubase_config_tm_vl_sch); + +int ubase_set_priqos_info(struct device *dev, struct ubase_sl_priqos *sl_priqos) +{ + struct ubase_dev *udev; + + if (!dev || !sl_priqos || !sl_priqos->sl_bitmap) + return -EINVAL; + + udev = dev_get_drvdata(dev); + + if (sl_priqos->port_bitmap) + return ubase_set_ets_priqos(udev, sl_priqos); + + return ubase_set_tm_priqos(udev, sl_priqos); +} +EXPORT_SYMBOL(ubase_set_priqos_info); + +int ubase_get_priqos_info(struct device *dev, struct ubase_sl_priqos *sl_priqos) +{ + struct ubase_dev *udev; + + if (!dev || !sl_priqos || !sl_priqos->sl_bitmap) + return -EINVAL; + + udev = dev_get_drvdata(dev); + + if (sl_priqos->port_bitmap) + return ubase_get_ets_priqos(udev, sl_priqos); + + return ubase_get_tm_priqos(udev, sl_priqos); +} +EXPORT_SYMBOL(ubase_get_priqos_info); + +static int ubase_query_vl_ageing(struct ubase_dev *udev, u16 *vl_ageing_en) +{ + struct ubase_query_vl_ageing_cmd resp = {0}; + struct ubase_query_vl_ageing_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_VL_AGEING_EN, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_VL_AGEING_EN, false, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to query vl ageing configuration, ret = %d.\n", + ret); + return ret; + } + + *vl_ageing_en = le16_to_cpu(resp.vl_ageing_en); + + ubase_dbg(udev, "vl_ageing_en bitmap:%u.\n", *vl_ageing_en); + + return 0; +} + +static int ubase_query_ctp_vl_offset(struct ubase_dev *udev, u8 *ctp_vl_offset) +{ + struct ubase_query_ctp_vl_offset_cmd resp = {0}; + struct ubase_query_ctp_vl_offset_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_CTP_VL_OFFSET, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_CTP_VL_OFFSET, false, + sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, + "failed to query ctp vl offset, ret = %d.\n", ret); + return ret; + } + + *ctp_vl_offset = resp.ctp_vl_offset; + + ubase_dbg(udev, "ctp_vl_offset:%u.\n", *ctp_vl_offset); + + return 0; +} + +static inline void ubase_parse_udma_req_vl_uboe(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + + qos->tp_vl_num = qos->vl_num; + memcpy(qos->tp_req_vl, qos->vl, qos->vl_num); +} + +static int ubase_parse_udma_req_vl_ub(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + unsigned long vl_ageing_en; + int ret; + u8 i; + + ret = ubase_query_vl_ageing(udev, (u16 *)&vl_ageing_en); + if (ret) + return ret; + + for (i = 0; i < qos->vl_num; i++) { + if (test_bit(qos->vl[i], &vl_ageing_en)) + qos->tp_req_vl[qos->tp_vl_num++] = + qos->vl[i]; + else + qos->ctp_req_vl[qos->ctp_vl_num++] = + qos->vl[i]; + } + + return 0; +} + +static int ubase_parse_udma_req_vl(struct ubase_dev *udev) +{ + if (ubase_dev_ubl_supported(udev)) + return ubase_parse_udma_req_vl_ub(udev); + + ubase_parse_udma_req_vl_uboe(udev); + return 0; +} + +static int ubase_check_ctp_resp_vl(struct ubase_dev *udev, u8 ctp_vl_offset) +{ + struct ubase_adev_qos *qos = &udev->qos; + u8 i, ctp_resp_vl_off; + + for (i = 0; i < qos->ctp_vl_num; i++) { + ctp_resp_vl_off = qos->ctp_req_vl[i] + ctp_vl_offset; + if (ctp_resp_vl_off >= UBASE_MAX_VL_NUM) { + ubase_err(udev, + "the %uth ctp_resp_vl(%u) exceed max_vl_num(%u).\n", + i, ctp_resp_vl_off, UBASE_MAX_VL_NUM); + return -EINVAL; + } + } + + return 0; +} + +static int ubase_parse_ctp_resp_vl(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + u8 ctp_vl_offset; + int ret; + + ret = ubase_query_ctp_vl_offset(udev, &ctp_vl_offset); + if (ret) + return ret; + + ret = ubase_check_ctp_resp_vl(udev, ctp_vl_offset); + if (ret) + return ret; + + qos->ctp_resp_vl_offset = ctp_vl_offset; + + return 0; +} + +static bool ubase_get_vl_sl(struct ubase_dev *udev, u8 vl, u8 *sl, u8 *sl_num) +{ + bool sl_exist = false; + u8 i; + + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (udev->qos.ue_sl_vl[i] == vl) { + sl[(*sl_num)++] = i; + sl_exist = true; + } + } + + return sl_exist; +} + +static int ubase_parse_udma_tp_sl(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + bool exist; + u8 i; + + for (i = 0; i < qos->tp_vl_num; i++) { + exist = ubase_get_vl_sl(udev, qos->tp_req_vl[i], + qos->tp_sl, &qos->tp_sl_num); + if (!exist) { + ubase_err(udev, + "udma tp req vl(%u) doesn't have a corresponding sl.\n", + qos->tp_req_vl[i]); + return -EINVAL; + } + } + + return 0; +} + +static int ubase_parse_udma_ctp_sl(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + bool exist; + u8 i; + + for (i = 0; i < qos->ctp_vl_num; i++) { + exist = ubase_get_vl_sl(udev, qos->ctp_req_vl[i], + qos->ctp_sl, &qos->ctp_sl_num); + if (!exist) { + ubase_err(udev, + "udma ctp req vl(%u) doesn't have a corresponding sl.\n", + qos->ctp_req_vl[i]); + return -EINVAL; + } + } + + return 0; +} + +static int ubase_parse_udma_sl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_parse_udma_tp_sl(udev); + if (ret) + return ret; + + return ubase_parse_udma_ctp_sl(udev); +} + +static void ubase_gather_udma_req_resp_vl(struct ubase_dev *udev, + u8 *req_vl, u8 req_vl_num, + u8 resp_vl_off) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_adev_qos *qos = &udev->qos; + u8 i, j; + + for (i = 0; i < req_vl_num; i++) { + for (j = 0; j < qos->nic_vl_num; j++) { + if (req_vl[i] == dev_caps->req_vl[j]) { + dev_caps->resp_vl[j] = req_vl[i] + resp_vl_off; + break; + } + } + + if (j < qos->nic_vl_num) + continue; + + dev_caps->req_vl[dev_caps->vl_num] = req_vl[i]; + dev_caps->resp_vl[dev_caps->vl_num] = req_vl[i] + resp_vl_off; + dev_caps->vl_num++; + } +} + +static void ubase_gather_urma_req_resp_vl(struct ubase_dev *udev) +{ + struct ubase_caps *dev_caps = &udev->caps.dev_caps; + struct ubase_adev_qos *qos = &udev->qos; + + memcpy(dev_caps->req_vl, qos->nic_vl, qos->nic_vl_num); + memcpy(dev_caps->resp_vl, qos->nic_vl, qos->nic_vl_num); + dev_caps->vl_num = qos->nic_vl_num; + + /* Restriction: The unic vl can't be used as the dma resp vl. */ + ubase_gather_udma_req_resp_vl(udev, qos->tp_req_vl, + qos->tp_vl_num, + qos->tp_resp_vl_offset); + ubase_gather_udma_req_resp_vl(udev, qos->ctp_req_vl, + qos->ctp_vl_num, + qos->ctp_resp_vl_offset); + + /* dev_caps->vl_num is used for DCB tool configuration. Therefore, + * dev_caps->vl_num cannot exceed IEEE_8021QAZ_MAX_TCS. + */ + dev_caps->vl_num = min(dev_caps->vl_num, IEEE_8021QAZ_MAX_TCS); +} + +static inline void ubase_gather_cdma_req_resp_vl(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + + ubase_gather_udma_req_resp_vl(udev, qos->ctp_req_vl, + qos->ctp_vl_num, + qos->ctp_resp_vl_offset); +} + +static inline int ubase_parse_udma_resp_vl(struct ubase_dev *udev) +{ + if (ubase_dev_ubl_supported(udev)) + return ubase_parse_ctp_resp_vl(udev); + + return 0; +} + +static int ubase_assign_urma_vl(struct ubase_dev *udev, u8 *urma_sl, + u8 urma_sl_num, u8 *urma_vl, u8 *urma_vl_num) +{ + u8 urma_vl_bitmap[UBASE_MAX_VL_NUM] = {0}; + u8 i, current_vl; + int index = 0; + + for (i = 0; i < urma_sl_num; i++) { + current_vl = udev->qos.ue_sl_vl[urma_sl[i]]; + if (current_vl >= UBASE_MAX_VL_NUM) { + ubase_err(udev, + "urma vl(%u) exceeds the maximum(%u).\n", + current_vl, UBASE_MAX_VL_NUM); + return -EINVAL; + } + + if (urma_vl_bitmap[current_vl] == 0) { + urma_vl[index++] = current_vl; + urma_vl_bitmap[current_vl] = 1; + (*urma_vl_num)++; + } + } + + return 0; +} + +static int ubase_parse_rack_nic_vl(struct ubase_dev *udev) +{ + return ubase_assign_urma_vl(udev, udev->qos.nic_sl, udev->qos.nic_sl_num, + udev->qos.nic_vl, &udev->qos.nic_vl_num); +} + +static int ubase_parse_rack_udma_req_vl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_assign_urma_vl(udev, udev->qos.sl, + udev->qos.sl_num, udev->qos.vl, + &udev->qos.vl_num); + if (ret) + return ret; + + return ubase_parse_udma_req_vl(udev); +} + +static int ubase_parse_rack_udma_vl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_parse_rack_udma_req_vl(udev); + if (ret) + return ret; + + return ubase_parse_udma_resp_vl(udev); +} + +static int ubase_parse_rack_cdma_resp_vl(struct ubase_dev *udev) +{ + return ubase_parse_ctp_resp_vl(udev); +} + +static int ubase_parse_rack_cdma_req_sl_vl(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + bool exist = false; + u8 i; + + for (i = 0; i < qos->vl_num; i++) { + exist = ubase_get_vl_sl(udev, qos->vl[i], qos->ctp_sl, + &qos->ctp_sl_num); + if (exist) + qos->ctp_req_vl[qos->ctp_vl_num++] = qos->vl[i]; + } + + if (!qos->ctp_vl_num) { + ubase_err(udev, "cdma doesn't have any req vl.\n"); + return -EINVAL; + } + + return 0; +} + +static int ubase_parse_rack_cdma_sl_vl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_parse_rack_cdma_req_sl_vl(udev); + if (ret) + return ret; + + ret = ubase_parse_rack_cdma_resp_vl(udev); + if (ret) + return ret; + + ubase_gather_cdma_req_resp_vl(udev); + return 0; +} + +static inline int ubase_parse_rack_nic_sl_vl(struct ubase_dev *udev) +{ + return ubase_parse_rack_nic_vl(udev); +} + +static inline int ubase_parse_rack_udma_sl_vl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_parse_rack_udma_vl(udev); + if (ret) + return ret; + + return ubase_parse_udma_sl(udev); +} + +static int ubase_parse_rack_urma_sl_vl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_parse_rack_nic_sl_vl(udev); + if (ret) + return ret; + + if (ubase_dev_udma_supported(udev)) { + ret = ubase_parse_rack_udma_sl_vl(udev); + if (ret) + return ret; + } + + ubase_gather_urma_req_resp_vl(udev); + return 0; +} + +static int ubase_parse_rack_adev_sl_vl(struct ubase_dev *udev) +{ + if (ubase_dev_cdma_supported(udev)) + return ubase_parse_rack_cdma_sl_vl(udev); + + if (ubase_dev_urma_supported(udev)) + return ubase_parse_rack_urma_sl_vl(udev); + + return 0; +} + +static void ubase_init_udma_dscp_vl(struct ubase_dev *udev) +{ + struct ubase_adev_qos *qos = &udev->qos; + u8 i; + + for (i = 0; i < UBASE_MAX_DSCP; i++) + qos->dscp_vl[i] = qos->tp_req_vl[0]; +} + +static void ubase_parse_max_vl(struct ubase_dev *udev) +{ + struct ubase_adev_caps *udma_caps = &udev->caps.udma_caps; + struct ubase_adev_qos *qos = &udev->qos; + u8 i, max_vl = 0; + + for (i = 0; i < qos->nic_vl_num; i++) + max_vl = max(qos->nic_vl[i], max_vl); + + for (i = 0; i < qos->tp_vl_num; i++) + max_vl = max(qos->tp_req_vl[i] + + qos->tp_resp_vl_offset, max_vl); + + for (i = 0; i < qos->ctp_vl_num; i++) + max_vl = max(qos->ctp_req_vl[i] + + qos->ctp_resp_vl_offset, max_vl); + + qos->ue_max_vl_id = max_vl; + + if (ubase_dev_urma_supported(udev)) + udma_caps->rc_max_cnt *= (max_vl + 1); +} + +static int ubase_parse_sl_vl(struct ubase_dev *udev) +{ + int ret; + + ret = ubase_query_sl_vl_map(udev, udev->qos.ue_sl_vl); + if (ret) + return ret; + + ret = ubase_parse_rack_adev_sl_vl(udev); + if (ret) + return ret; + + if (ubase_dev_udma_supported(udev)) + ubase_init_udma_dscp_vl(udev); + + if (ubase_utp_supported(udev) && ubase_dev_urma_supported(udev)) + udev->caps.unic_caps.tpg.max_cnt = udev->qos.nic_vl_num; + + ubase_parse_max_vl(udev); + + return 0; +} + +static int ubase_ctrlq_query_vl(struct ubase_dev *udev) +{ + struct ubase_ctrlq_query_vl_resp resp = {0}; + struct ubase_ctrlq_query_vl_req req = {0}; + struct ubase_ctrlq_msg msg = {0}; + unsigned long vl_bitmap; + u8 i, vl_cnt = 0; + int ret; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_QOS; + msg.opcode = UBASE_CTRLQ_OPC_QUERY_VL; + msg.need_resp = 1; + msg.is_resp = 0; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) { + ubase_err(udev, + "failed to send ctrlq msg when query vl, ret = %d.\n", ret); + return ret; + } + + vl_bitmap = le16_to_cpu(resp.vl_bitmap); + + for (i = 0; i < UBASE_MAX_VL_NUM; i++) + if (test_bit(i, &vl_bitmap)) + udev->qos.vl[vl_cnt++] = i; + + if (!vl_cnt) + return -EBUSY; + + udev->qos.vl_num = vl_cnt; + + ubase_dbg(udev, "ctrlq query vl_bitmap = %lx.\n", vl_bitmap); + + return 0; +} + +static int ubase_ctrlq_query_sl(struct ubase_dev *udev) +{ + struct ubase_ctrlq_query_sl_resp resp = {0}; + struct ubase_ctrlq_query_sl_req req = {0}; + u8 i, unic_sl_cnt = 0, udma_sl_cnt = 0; + struct ubase_ctrlq_msg msg = {0}; + unsigned long unic_sl_bitmap; + unsigned long udma_sl_bitmap; + int ret; + + msg.service_ver = UBASE_CTRLQ_SER_VER_01; + msg.service_type = UBASE_CTRLQ_SER_TYPE_QOS; + msg.opcode = UBASE_CTRLQ_OPC_QUERY_SL; + msg.need_resp = 1; + msg.is_resp = 0; + msg.in_size = sizeof(req); + msg.in = &req; + msg.out_size = sizeof(resp); + msg.out = &resp; + + ret = __ubase_ctrlq_send(udev, &msg, NULL); + if (ret) { + ubase_err(udev, + "failed to send ctrlq msg when query sl, ret = %d.\n", ret); + return ret; + } + + unic_sl_bitmap = le16_to_cpu(resp.unic_sl_bitmap); + udma_sl_bitmap = le16_to_cpu(resp.udma_sl_bitmap); + + for (i = 0; i < UBASE_MAX_SL_NUM; i++) { + if (test_bit(i, &unic_sl_bitmap)) + udev->qos.nic_sl[unic_sl_cnt++] = i; + if (test_bit(i, &udma_sl_bitmap)) + udev->qos.sl[udma_sl_cnt++] = i; + } + + if (!unic_sl_cnt) { + ubase_err(udev, "nic doesn't have any sl.\n"); + return -EIO; + } + + if (ubase_dev_udma_supported(udev) && !udma_sl_cnt) { + ubase_err(udev, "udma doesn't have any sl.\n"); + return -EIO; + } + + udev->qos.nic_sl_num = unic_sl_cnt; + udev->qos.sl_num = udma_sl_cnt; + + ubase_dbg(udev, "ctrlq query unic_sl_bitmap = 0x%lx, udma_sl_bitmap = 0x%lx.\n", + unic_sl_bitmap, udma_sl_bitmap); + + return 0; +} + +int ubase_qos_init(struct ubase_dev *udev) +{ + int ret = 0; + + if (ubase_dev_urma_supported(udev)) + ret = ubase_ctrlq_query_sl(udev); + else if (ubase_dev_cdma_supported(udev)) + ret = ubase_ctrlq_query_vl(udev); + + if (ret) + return ret; + + if (ubase_dev_pmu_supported(udev)) + return 0; + + return ubase_parse_sl_vl(udev); +} + +static bool ubase_is_udma_tp_vl(struct ubase_adev_qos *qos, u8 vl) +{ + u8 i; + + for (i = 0; i < qos->tp_vl_num; i++) { + if (qos->tp_req_vl[i] == vl) + return true; + } + + return false; +} + +void ubase_update_udma_dscp_vl(struct auxiliary_device *adev, u8 *dscp_vl, + u8 dscp_num) +{ + struct ubase_adev_qos *qos; + u8 i, arr_len; + + if (!adev || !dscp_vl) + return; + + qos = ubase_get_adev_qos(adev); + arr_len = min(UBASE_MAX_DSCP, dscp_num); + + for (i = 0; i < arr_len; i++) + qos->dscp_vl[i] = ubase_is_udma_tp_vl(qos, dscp_vl[i]) ? + dscp_vl[i] : qos->tp_req_vl[0]; +} +EXPORT_SYMBOL(ubase_update_udma_dscp_vl); + +int ubase_query_tm_queue(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_queue_cmd *resp) +{ + struct ubase_query_tm_queue_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.bus_ue_id = cpu_to_le16(bus_ue_id); + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_TM_Q_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_TM_Q_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret == -EPERM) + return -EOPNOTSUPP; + if (ret) + ubase_err(udev, + "failed to query tm queue info, bus_ue_id=%u, ret=%d.\n", + bus_ue_id, ret); + return ret; +} + +int ubase_query_tm_qset(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_qset_cmd *resp) +{ + struct ubase_query_tm_qset_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.bus_ue_id = cpu_to_le16(bus_ue_id); + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_TM_QS_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_TM_QS_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret == -EPERM) + return -EOPNOTSUPP; + if (ret) + ubase_err(udev, + "failed to query tm qset info, bus_ue_id = %u, ret = %d.\n", + bus_ue_id, ret); + return ret; +} + +int ubase_query_tm_pri(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_pri_cmd *resp) +{ + struct ubase_query_tm_pri_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.bus_ue_id = cpu_to_le16(bus_ue_id); + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_TM_PRI_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_TM_PRI_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret == -EPERM) + return -EOPNOTSUPP; + if (ret) + ubase_err(udev, + "failed to query tm pri info, bus_ue_id = %u, ret = %d.\n", + bus_ue_id, ret); + return ret; +} + +int ubase_query_tm_pg(struct ubase_dev *udev, u16 bus_ue_id, + struct ubase_query_tm_pg_cmd *resp) +{ + struct ubase_query_tm_pg_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + req.bus_ue_id = cpu_to_le16(bus_ue_id); + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_TM_PG_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_TM_PG_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret == -EPERM) + return -EOPNOTSUPP; + if (ret) + ubase_err(udev, + "failed to query tm pg info, bus_ue_id = %u, ret = %d.\n", + bus_ue_id, ret); + return ret; +} + +int ubase_query_tm_port(struct ubase_dev *udev, + struct ubase_query_tm_port_cmd *resp) +{ + struct ubase_query_tm_port_cmd req = {0}; + struct ubase_cmd_buf in, out; + int ret; + + ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_TM_PORT_INFO, true, + sizeof(req), &req); + ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_TM_PORT_INFO, false, + sizeof(*resp), resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret == -EPERM) + return -EOPNOTSUPP; + if (ret) + ubase_err(udev, "failed to query tm port info, ret = %d.\n", ret); + return ret; +} diff --git a/drivers/ub/ubase/ubase_reset.c b/drivers/ub/ubase/ubase_reset.c new file mode 100644 index 0000000000000000000000000000000000000000..bb1c281c02d378912fd58e7141843d266933f867 --- /dev/null +++ b/drivers/ub/ubase/ubase_reset.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include + +#include "debugfs/ubase_debugfs.h" +#include "ubase.h" +#include "ubase_cmd.h" +#include "ubase_ctrlq.h" +#include "ubase_hw.h" +#include "ubase_mailbox.h" +#include "ubase_ubus.h" +#include "ubase_reset.h" + +static void ubase_reset_task_schedule(struct ubase_dev *udev) +{ + if (!test_and_set_bit(UBASE_SERVICE_STATE_RESET_SCHED, + &udev->service_task.state)) { + udev->last_reset_scheduled = jiffies; + mod_delayed_work(udev->ubase_reset_wq, + &udev->reset_service_task.service_task, 0); + } +} + +static void ubase_reset_err_handle(struct ubase_dev *udev) +{ + udev->reset_stat.reset_fail_cnt++; + udev->reset_stat.reset_retry_cnt++; + if (udev->reset_stat.reset_retry_cnt < UBASE_RST_MAX_RETRY_CNT) { + ubase_reset_task_schedule(udev); + ubase_info(udev, "re-schedule reset task(%u).\n", + udev->reset_stat.reset_retry_cnt); + return; + } + + ubase_err(udev, "failed to reset, too many attempts.\n"); +} + +void ubase_port_reset(struct ubase_dev *udev) +{ + ubase_port_down(udev); + ubase_port_up(udev); + udev->reset_stat.port_reset_cnt++; +} + +void ubase_reset_service(struct ubase_delay_work *ubase_work) +{ + struct ubase_dev *udev = container_of(ubase_work, struct ubase_dev, + reset_service_task); + int ret; + + if (!test_and_clear_bit(UBASE_SERVICE_STATE_RESET_SCHED, + &udev->service_task.state)) + return; + + if (time_is_before_eq_jiffies(udev->last_reset_scheduled + + UBASE_RESET_SCHED_TIMEOUT)) + ubase_warn(udev, + "reset service task scheduled after %ums on cpu%d!\n", + jiffies_to_msecs(jiffies - udev->last_reset_scheduled), + smp_processor_id()); + + ret = ubase_ubus_reset_entry(udev->dev); + if (ret) + ubase_err(udev, "failed to reset hardware, ret = %d.\n", ret); +} + +void __ubase_reset_event(struct ubase_dev *udev, + enum ubase_reset_type reset_type) +{ + udev->reset_type = reset_type; + + switch (reset_type) { + case UBASE_UE_RESET: + case UBASE_ELR_RESET: + ubase_reset_task_schedule(udev); + break; + default: + ubase_err(udev, "unknown reset type = %u, do nothing.\n", + reset_type); + break; + } +} + +void ubase_reset_event(struct auxiliary_device *adev, + enum ubase_reset_type reset_type) +{ + struct ubase_dev *udev; + + if (!adev) + return; + + udev = __ubase_get_udev_by_adev(adev); + + __ubase_reset_event(udev, reset_type); +} +EXPORT_SYMBOL(ubase_reset_event); + +static int ubase_notify_ue_reset(struct ubase_dev *udev, u16 bus_ue_id, + u16 single) +{ + struct ubase_notify_ue_reset_cmd req = {0}; + struct ubase_cmd_buf in; + int ret; + + req.bus_ue_id = bus_ue_id; + req.single = single; + __ubase_fill_inout_buf(&in, UBASE_OPC_NOTIFY_UE_RESET, false, + sizeof(req), &req); + + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, "failed to notify ue%hu reset, ret = %d.\n", + bus_ue_id, ret); + + return ret; +} + +static void ubase_notify_all_ue_reset(struct ubase_dev *udev) +{ + struct ubase_ue_node *ue_node; + + list_for_each_entry(ue_node, &udev->ue_list, list) + ubase_notify_ue_reset(udev, ue_node->bus_ue_id, 0); +} + +static void ubase_wait_ue_reset_ready(struct ubase_dev *udev) +{ + struct ubase_ue_reset_ready_cmd resp; + struct ubase_cmd_buf out, in; + int try_cnt = 0; + int ret; + + if (!dev_num_vf(udev->dev)) + return; + + do { + memset(&resp, 0, sizeof(resp)); + __ubase_fill_inout_buf(&in, UBASE_OPC_QUERY_UE_RST_RDY, true, + 0, NULL); + __ubase_fill_inout_buf(&out, UBASE_OPC_QUERY_UE_RST_RDY, + false, sizeof(resp), &resp); + + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, "failed to query ue ready status, ret = %d.\n", + ret); + return; + } + + msleep(UBASE_RST_WAIT_CMD_TIME); + try_cnt++; + } while (resp.ue_unready_num && try_cnt < UBASE_RST_WAIT_CMD_COUNT); + + if (resp.ue_unready_num) + ubase_warn(udev, "wait ue reset ready timeout! unready num = %u.\n", + resp.ue_unready_num); +} + +static int ubase_ue_reset_done_check(struct ubase_dev *udev) +{ + u32 reset_done_reg; + int try_cnt = 0; + + while (try_cnt < UBASE_RST_WAIT_REG_COUNT) { + reset_done_reg = ubase_read_dev(&udev->hw, UBASE_RST_ING_REG); + if (!(reset_done_reg & BIT(UBASE_RST_ING_RST_DONE_B))) + return 0; + + try_cnt++; + msleep(UBASE_RST_WAIT_REG_TIME); + } + + ubase_warn(udev, "wait reset done reg time out.\n"); + return -EBUSY; +} + +static void ubase_reset_done(struct ubase_dev *udev) +{ + struct ubase_cmd_buf in; + int ret; + + if (!dev_num_vf(udev->dev)) + return; + + __ubase_fill_inout_buf(&in, UBASE_OPC_RESET_DONE, false, 0, NULL); + + ret = __ubase_cmd_send_in(udev, &in); + if (ret) + ubase_err(udev, "failed to send reset done cmd, ret = %d.\n", + ret); + + /* Wait for entities to detect that its mue have reset done */ + msleep(UBASE_RST_UE_WAIT_REG_TIME); +} + +void ubase_suspend(struct ubase_dev *udev) +{ + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + test_and_set_bit(UBASE_STATE_DISABLED_B, &udev->state_bits)) { + ubase_warn(udev, + "failed to suspend ubase, device is not ready or removing.\n"); + return; + } + + set_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits); + + if (ubase_dev_pmu_supported(udev)) { + __ubase_cmd_disable(udev); + udev->reset_stat.elr_reset_cnt++; + return; + } + + ubase_notify_all_ue_reset(udev); + + udev->reset_stage = UBASE_RESET_STAGE_DOWN; + ubase_suspend_aux_devices(udev); + ubase_wait_ue_reset_ready(udev); + udev->reset_stage = UBASE_RESET_STAGE_UNINIT; + + udev->reset_stat.elr_reset_cnt++; + + clear_bit(UBASE_STATE_CTX_READY_B, &udev->state_bits); + ubase_cmd_disable(udev); + ubase_ctrlq_disable_remote(udev); + ubase_ctrlq_disable(udev); + ubase_irq_table_free(udev); +} + +void ubase_resume(struct ubase_dev *udev) +{ + int ret; + + if (!test_bit(UBASE_STATE_INITED_B, &udev->state_bits) || + !test_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits)) { + ubase_warn(udev, + "failed to resume ubase, device is not ready or removing.\n"); + return; + } + + if (ubase_dev_pmu_supported(udev)) { + ubase_ubus_reinit(udev->dev); + __ubase_cmd_enable(udev); + udev->reset_stat.reset_done_cnt++; + udev->reset_stat.hw_reset_done_cnt++; + clear_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits); + clear_bit(UBASE_STATE_DISABLED_B, &udev->state_bits); + return; + } + + udev->reset_stat.hw_reset_done_cnt++; + ubase_suspend_aux_devices(udev); + ubase_dev_reset_uninit(udev); + ubase_ubus_reinit(udev->dev); + + udev->reset_stage = UBASE_RESET_STAGE_NONE; + ret = ubase_ue_reset_done_check(udev); + if (ret) + goto err_resume; + + ret = ubase_dev_reset_init(udev); + if (ret) + goto err_resume; + + ubase_resume_aux_devices(udev); + ubase_reset_done(udev); + + udev->reset_stat.reset_done_cnt++; + udev->reset_stat.reset_retry_cnt = 0; + clear_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits); + clear_bit(UBASE_STATE_DISABLED_B, &udev->state_bits); + return; + +err_resume: + ubase_resume_aux_devices(udev); + clear_bit(UBASE_STATE_RST_HANDLING_B, &udev->state_bits); + clear_bit(UBASE_STATE_DISABLED_B, &udev->state_bits); + ubase_reset_err_handle(udev); +} diff --git a/drivers/ub/ubase/ubase_reset.h b/drivers/ub/ubase/ubase_reset.h new file mode 100644 index 0000000000000000000000000000000000000000..03d0f02bb9a8aef4de0741d5fdbeba374abb30f6 --- /dev/null +++ b/drivers/ub/ubase/ubase_reset.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_RESET_H__ +#define __UBASE_RESET_H__ + +#include "ubase_dev.h" + +#define UBASE_RST_ING_REG 0x00018040 +#define UBASE_RST_ING_RST_DONE_B 0 + +#define UBASE_RESET_SCHED_TIMEOUT (3 * HZ) +#define UBASE_RST_WAIT_REG_COUNT 60 +#define UBASE_RST_WAIT_REG_TIME 50 +#define UBASE_RST_WAIT_CMD_COUNT 60 +#define UBASE_RST_WAIT_CMD_TIME 50 + +#define UBASE_RST_UE_WAIT_REG_TIME 200 +#define UBASE_RST_MAX_RETRY_CNT 5 +#define UBASE_RST_WAIT_TIME 100 + +struct ubase_notify_ue_reset_cmd { + u16 bus_ue_id; + u16 single; + u8 rsv[20]; +}; + +struct ubase_ue_reset_ready_cmd { + u16 ue_unready_num; + u8 rsv[22]; +}; + +void ubase_suspend(struct ubase_dev *udev); +void ubase_resume(struct ubase_dev *udev); +void ubase_reset_service(struct ubase_delay_work *ubase_work); +void __ubase_reset_event(struct ubase_dev *udev, + enum ubase_reset_type reset_type); +void ubase_port_reset(struct ubase_dev *udev); + +#endif diff --git a/drivers/ub/ubase/ubase_stats.c b/drivers/ub/ubase/ubase_stats.c new file mode 100644 index 0000000000000000000000000000000000000000..7f536e0cd537e9fd5ff326efa179d90fbe6576fd --- /dev/null +++ b/drivers/ub/ubase/ubase_stats.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include "ubase_cmd.h" +#include "ubase_stats.h" + +static int ubase_update_mac_stats(struct ubase_dev *udev, u16 port_id, u64 *data, + u16 size, bool is_accumulate) +{ + u16 mac_stats_num = udev->caps.dev_caps.mac_stats_num; + struct ubase_query_mac_stats_cmd *cmd; + struct ubase_cmd_buf in, out; + u16 i, cmd_size; + int ret; + + if (!ubase_dev_mac_stats_supported(udev)) { + ubase_err(udev, "not support get mac stats.\n"); + return -EOPNOTSUPP; + } + + cmd_size = mac_stats_num * sizeof(u64) + sizeof(*cmd); + cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!cmd) { + ubase_err(udev, "failed to alloc cmdq out_regs.\n"); + return -ENOMEM; + } + + cmd->port_id = cpu_to_le16(port_id); + ubase_fill_inout_buf(&in, UBASE_OPC_STATS_MAC_ALL, true, cmd_size, cmd); + ubase_fill_inout_buf(&out, UBASE_OPC_STATS_MAC_ALL, true, cmd_size, cmd); + ret = __ubase_cmd_send_inout(udev, &in, &out); + if (ret) { + ubase_err(udev, "failed to get mac stats, ret = %d.\n", ret); + goto out_send_cmd_fail; + } + + mac_stats_num = min_t(u16, size, mac_stats_num); + if (is_accumulate) + for (i = 0; i < mac_stats_num; i++) + *data++ += le64_to_cpu(cmd->stats_val[i]); + else + for (i = 0; i < mac_stats_num; i++) + *data++ = le64_to_cpu(cmd->stats_val[i]); + +out_send_cmd_fail: + kfree(cmd); + + return ret; +} + +int ubase_get_ub_port_stats(struct auxiliary_device *adev, u16 port_id, + struct ubase_ub_dl_stats *data) +{ + struct ubase_dev *udev; + + if (!adev || !data) + return -EINVAL; + + udev = __ubase_get_udev_by_adev(adev); + + return ubase_update_mac_stats(udev, port_id, (u64 *)data, + sizeof(*data) / sizeof(u64), false); +} +EXPORT_SYMBOL(ubase_get_ub_port_stats); + +void ubase_update_activate_stats(struct ubase_dev *udev, bool activate, + int result) +{ + struct ubase_activate_dev_stats *record = &udev->stats.activate_record; + u64 idx, total; + + mutex_lock(&record->lock); + + if (activate) + record->act_cnt++; + else + record->deact_cnt++; + + total = record->act_cnt + record->deact_cnt; + idx = (total - 1) % UBASE_ACT_STAT_MAX_NUM; + record->stats[idx].activate = activate; + record->stats[idx].time = ktime_get_real_seconds(); + record->stats[idx].result = result; + + mutex_unlock(&record->lock); +} diff --git a/drivers/ub/ubase/ubase_stats.h b/drivers/ub/ubase/ubase_stats.h new file mode 100644 index 0000000000000000000000000000000000000000..a6826dd461c76ffaf03dc4ea78fac6dfe31d8494 --- /dev/null +++ b/drivers/ub/ubase/ubase_stats.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_STATS_H__ +#define __UBASE_STATS_H__ + +#include + +struct ubase_query_mac_stats_cmd { + __le16 port_id; + u8 resv[2]; + __le64 stats_val[]; +}; + +void ubase_update_activate_stats(struct ubase_dev *udev, bool activate, + int result); + +#endif /* _UBASE_STATS_H */ diff --git a/drivers/ub/ubase/ubase_tp.c b/drivers/ub/ubase/ubase_tp.c new file mode 100644 index 0000000000000000000000000000000000000000..2b11b39fb6a402a0cb98b094becd155c96efd4f8 --- /dev/null +++ b/drivers/ub/ubase/ubase_tp.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include "ubase_ctrlq_tp.h" +#include "ubase_reset.h" +#include "ubase_tp.h" + +int ubase_ae_tp_flush_done(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct ubase_event_nb *ev_nb = container_of(nb, struct ubase_event_nb, nb); + struct ubase_dev *udev = (struct ubase_dev *)ev_nb->back; + struct ubase_aeq_notify_info *info = data; + u32 tp_num; + + tp_num = info->aeqe->event.queue_event.num; + + return ubase_notify_tp_fd_by_ctrlq(udev, tp_num); +} + +int ubase_ae_tp_level_error(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct ubase_event_nb *ev_nb = container_of(nb, + struct ubase_event_nb, nb); + struct ubase_aeq_notify_info *info = data; + struct ubase_dev *udev = ev_nb->back; + u32 queue_num; + + queue_num = info->aeqe->event.queue_event.num; + ubase_err(udev, + "ubase recv tp level error, event_type = 0x%x, sub_type = 0x%x, queue_num = %u\n", + info->event_type, info->sub_type, queue_num); + + __ubase_reset_event(udev, UBASE_UE_RESET); + + return 0; +} + +int ubase_dev_init_tp_tpg(struct ubase_dev *udev) +{ + if (!ubase_utp_supported(udev) || !ubase_dev_urma_supported(udev)) + return 0; + + return ubase_dev_init_rack_tp_tpg(udev); +} + +void ubase_dev_uninit_tp_tpg(struct ubase_dev *udev) +{ + if (!ubase_utp_supported(udev) || !ubase_dev_urma_supported(udev)) + return; + + ubase_dev_uninit_rack_tp_tpg(udev); +} diff --git a/drivers/ub/ubase/ubase_tp.h b/drivers/ub/ubase/ubase_tp.h new file mode 100644 index 0000000000000000000000000000000000000000..0506e77c98f082e68bd0bc439067120b76403e80 --- /dev/null +++ b/drivers/ub/ubase/ubase_tp.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_TP_H__ +#define __UBASE_TP_H__ + +#include + +#include "ubase_dev.h" + +#define UBASE_TP_PORT_BITMAP_STEP 2 + +#define UBASE_WAIT_TP_FLUSH_TOTAL_STEPS 12 + +struct ubase_tp_ctx { + u32 rsvd0; + u32 wqe_ba_l; + u32 wqe_ba_h : 20; + u32 rsvd1 : 12; + u32 rsvd2[5]; + u32 rsvd3_0 : 4; + u32 tp_wqe_token_id : 20; + u32 rsvd3_1 : 8; + u32 rsvd4[5]; + u32 rsvd5 : 4; + u32 reorder_q_addr_l : 28; + u32 reorder_q_addr_h : 24; + u32 rsvd6 : 8; + u32 rsvd7[5]; + u32 scc_token : 19; + u32 rsvd8 : 13; + u32 rsvd9[4]; + u32 rsvd10_0 : 24; + u32 scc_token_1 : 4; + u32 rsvd10_1 : 4; + u32 rsvd11[37]; +}; + +struct ubase_tpg { + u32 mb_tpgn; + u8 tpg_state; + u8 vl; + atomic_t tp_fd_cnt; + u8 tp_cnt; + unsigned long valid_tp; + u32 start_tpn; + u32 tp_shift; +}; + +int ubase_ae_tp_flush_done(struct notifier_block *nb, unsigned long event, + void *data); +int ubase_ae_tp_level_error(struct notifier_block *nb, unsigned long event, + void *data); +int ubase_dev_init_tp_tpg(struct ubase_dev *udev); +void ubase_dev_uninit_tp_tpg(struct ubase_dev *udev); + +#endif diff --git a/drivers/ub/ubase/ubase_trace.h b/drivers/ub/ubase/ubase_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..8d9cbe3a1c3fb3ceb7a409f2d052ccaae8a28891 --- /dev/null +++ b/drivers/ub/ubase/ubase_trace.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +/* This must be outside ifdef UBASE_TRACE_H_ */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ubase + +#if !defined(__UBASE_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __UBASE_TRACE_H__ + +#include + +#define TRACE_TWO_BYTES_BITS 16 +#define TRACE_THREE_BYTES_BITS 24 +#define TRACE_CMDQ_DESC_SIZE (sizeof(struct ubase_cmdq_desc) / sizeof(u32)) + +#define TRACE_DEV_NAME_MAX_LEN 16 + +DECLARE_EVENT_CLASS(ubase_cmdq_template, + TP_PROTO(const struct device *dev, int idx, u32 pi, u32 ci, + struct ubase_cmdq_desc *desc), + TP_ARGS(dev, idx, pi, ci, desc), + + TP_STRUCT__entry( + __field(int, idx) + __field(u32, pi) + __field(u32, ci) + __array(u32, data, TRACE_CMDQ_DESC_SIZE) + __dynamic_array(char, devname, TRACE_DEV_NAME_MAX_LEN) + ), + + TP_fast_assign( + __entry->idx = idx; + __entry->pi = pi; + __entry->ci = ci; + memcpy(&__entry->data, &desc[idx], + sizeof(u32) * TRACE_CMDQ_DESC_SIZE); + if (dev) { + snprintf(__get_str(devname), TRACE_DEV_NAME_MAX_LEN, + "%s %s", dev_driver_string(dev), dev_name(dev)); + } + ubase_mask_key_words((struct ubase_cmdq_desc *)&__entry->data, + desc[0].opcode, idx); + ), + + TP_printk( + "%s %d-%u-%u data: %s", __get_str(devname), + __entry->idx, __entry->pi, __entry->ci, + __print_array(__entry->data, TRACE_CMDQ_DESC_SIZE, sizeof(u32)) + ) +); + +DEFINE_EVENT(ubase_cmdq_template, ubase_csq_tx, + TP_PROTO(const struct device *dev, int idx, u32 pi, u32 ci, + struct ubase_cmdq_desc *desc), + TP_ARGS(dev, idx, pi, ci, desc)); + +DEFINE_EVENT(ubase_cmdq_template, ubase_csq_rx, + TP_PROTO(const struct device *dev, int idx, u32 pi, u32 ci, + struct ubase_cmdq_desc *desc), + TP_ARGS(dev, idx, pi, ci, desc)); + +TRACE_EVENT(ubase_crq, + TP_PROTO(const struct device *dev, int idx, u32 pi, u32 ci, + struct ubase_cmdq_desc *desc), + TP_ARGS(dev, idx, pi, ci, desc), + + TP_STRUCT__entry( + __field(int, idx) + __field(u32, pi) + __field(u32, ci) + __array(u32, data, TRACE_CMDQ_DESC_SIZE) + __dynamic_array(char, devname, TRACE_DEV_NAME_MAX_LEN) + ), + + TP_fast_assign( + __entry->idx = idx; + __entry->pi = pi; + __entry->ci = ci; + memcpy(&__entry->data, desc, + sizeof(u32) * TRACE_CMDQ_DESC_SIZE); + if (dev) { + snprintf(__get_str(devname), TRACE_DEV_NAME_MAX_LEN, + "%s %s", dev_driver_string(dev), dev_name(dev)); + } + ), + + TP_printk( + "%s %d-%u-%u data: %s", __get_str(devname), + __entry->idx, __entry->pi, __entry->ci, + __print_array(__entry->data, TRACE_CMDQ_DESC_SIZE, sizeof(u32)) + ) +); + +TRACE_EVENT(ubase_aeqe, + TP_PROTO(struct device *dev, struct ubase_aeqe *aeqe, + struct ubase_eq *eq), + TP_ARGS(dev, aeqe, eq), + + TP_STRUCT__entry( + __field(u32, event_type) + __field(u32, sub_type) + __field(u32, owner) + __field(u32, ci) + __field(u32, queue_event_num) + __field(u64, cmd_out_param) + __field(u16, cmd_seq_num) + __field(u8, cmd_status) + __dynamic_array(char, devname, TRACE_DEV_NAME_MAX_LEN) + ), + + TP_fast_assign( + __entry->event_type = aeqe->event_type; + __entry->sub_type = aeqe->sub_type; + __entry->owner = aeqe->owner; + __entry->ci = eq->cons_index; + __entry->queue_event_num = aeqe->event.queue_event.num; + __entry->cmd_out_param = aeqe->event.cmd.out_param; + __entry->cmd_seq_num = aeqe->event.cmd.seq_num; + __entry->cmd_status = aeqe->event.cmd.status; + if (dev) { + snprintf(__get_str(devname), TRACE_DEV_NAME_MAX_LEN, + "%s %s", dev_driver_string(dev), dev_name(dev)); + } + ), + + TP_printk( + "%s %u-%u-%u-%u-%u-%llu-%u-%u", __get_str(devname), + __entry->event_type, __entry->sub_type, __entry->owner, + __entry->ci, __entry->queue_event_num, __entry->cmd_out_param, + __entry->cmd_seq_num, __entry->cmd_status + ) +); + +TRACE_EVENT(ubase_ceqe, + TP_PROTO(struct device *dev, u32 jfcn, struct ubase_eq *eq), + TP_ARGS(dev, jfcn, eq), + + TP_STRUCT__entry( + __field(u32, jfcn) + __field(u32, ci) + __dynamic_array(char, devname, TRACE_DEV_NAME_MAX_LEN) + ), + + TP_fast_assign( + __entry->jfcn = jfcn; + __entry->ci = eq->cons_index; + if (dev) { + snprintf(__get_str(devname), TRACE_DEV_NAME_MAX_LEN, + "%s %s", dev_driver_string(dev), dev_name(dev)); + } + ), + + TP_printk( + "%s %u-%u", __get_str(devname), __entry->jfcn, __entry->ci + ) +); + +DECLARE_EVENT_CLASS(ubase_ctrlq_template, + TP_PROTO(const struct device *dev, u16 bb_num, u16 pi, u16 ci, + const void *buf, u16 len), + TP_ARGS(dev, bb_num, pi, ci, buf, len), + + TP_STRUCT__entry( + __field(u16, bb_num) + __field(u16, pi) + __field(u16, ci) + __dynamic_array(u8, data, len) + __field(u16, len) + __dynamic_array(char, devname, TRACE_DEV_NAME_MAX_LEN) + ), + + TP_fast_assign( + __entry->bb_num = bb_num; + __entry->pi = pi; + __entry->ci = ci; + __entry->len = len; + memcpy(__get_dynamic_array(data), buf, len); + if (dev) { + snprintf(__get_str(devname), TRACE_DEV_NAME_MAX_LEN, + "%s %s", dev_driver_string(dev), dev_name(dev)); + } + ), + + TP_printk( + "%s %u-%u-%u data: %s", __get_str(devname), + __entry->bb_num, __entry->pi, __entry->ci, + __print_array(__get_dynamic_array(data), __entry->len, sizeof(__u8)) + ) +); + +DEFINE_EVENT(ubase_ctrlq_template, ubase_ctrlq_csq, + TP_PROTO(const struct device *dev, u16 bb_num, u16 pi, u16 ci, + const void *buf, u16 len), + TP_ARGS(dev, bb_num, pi, ci, buf, len)); + +DEFINE_EVENT(ubase_ctrlq_template, ubase_ctrlq_crq, + TP_PROTO(const struct device *dev, u16 bb_num, u16 pi, u16 ci, + const void *buf, u16 len), + TP_ARGS(dev, bb_num, pi, ci, buf, len)); + +#endif /* __UBASE_TRACE_H__ */ + +/* This must be outside ifdef __UBASE_TRACE_H__ */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE ubase_trace +#include diff --git a/drivers/ub/ubase/ubase_ubus.c b/drivers/ub/ubase/ubase_ubus.c new file mode 100644 index 0000000000000000000000000000000000000000..a589915cd68663ee0be874f4ee4fb198041e583d --- /dev/null +++ b/drivers/ub/ubase/ubase_ubus.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#include +#include +#include + +#include "ubase_cmd.h" +#include "ubase_dev.h" +#include "ubase_reset.h" +#include "ubase_ubus.h" + +static const char ubase_ubus_driver_name[] = "ubase"; + +static const struct ub_device_id ubase_ubus_tbl[] = { + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_K_0_URMA_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_K_0_URMA_UE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_K_0_CDMA_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_K_0_CDMA_UE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_K_0_PMU_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_K_0_PMU_UE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_URMA_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_URMA_UE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_CDMA_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_CDMA_UE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_PMU_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_PMU_UE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_UBOE_MUE), 0, 0}, + {UB_ENTITY(UBASE_VENDOR_ID, UBASE_DEV_ID_A_0_UBOE_UE), 0, 0}, + /* required last entry */ + {0}, +}; +MODULE_DEVICE_TABLE(ub, ubase_ubus_tbl); + +static int ubase_ubus_init(struct ub_entity *ue) +{ +#define UBASE_UBUS_DMA_BIT 48 + + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + int ret; + + ub_entity_enable(ue, 1); + + ret = dma_set_mask_and_coherent(&ue->dev, + DMA_BIT_MASK(UBASE_UBUS_DMA_BIT)); + if (ret) { + ubase_err(udev, + "can't set consistent UBUS DMA, ret = %d.\n", ret); + goto err_enable_device; + } + + udev->hw.io_base.addr_unmapped = ub_resource_start(ue, UBASE_UBUS_IO_RESOURCE); + udev->hw.io_base.addr = ub_iomap(ue, UBASE_UBUS_IO_RESOURCE, 0); + if (!udev->hw.io_base.addr) { + ubase_err(udev, "failed to map io base.\n"); + ret = -ENOMEM; + goto err_enable_device; + } + + udev->hw.mem_base.addr_unmapped = ub_resource_start(ue, UBASE_UBUS_MEM_RESOURCE); + udev->hw.mem_base.addr = devm_ioremap_wc(&ue->dev, + ub_resource_start(ue, UBASE_UBUS_MEM_RESOURCE), + ub_resource_len(ue, UBASE_UBUS_MEM_RESOURCE)); + if (!udev->hw.mem_base.addr) { + ubase_err(udev, "failed to map memory base.\n"); + ret = -ENOMEM; + goto err_unmap_io_base; + } + + udev->hw.rs0_base.addr_unmapped = ub_resource_start(ue, UBASE_UBUS_RESOURCE_0); + udev->hw.rs0_base.addr = ub_iomap(ue, UBASE_UBUS_RESOURCE_0, 0); + if (!udev->hw.rs0_base.addr) { + ubase_err(udev, "failed to map resource0 addr.\n"); + ret = -ENOMEM; + goto err_unmap_mem_base; + } + + return 0; + +err_unmap_mem_base: + devm_iounmap(&ue->dev, udev->hw.mem_base.addr); + udev->hw.mem_base.addr = NULL; +err_unmap_io_base: + ub_iounmap(udev->hw.io_base.addr); + udev->hw.io_base.addr = NULL; +err_enable_device: + ub_entity_enable(ue, 0); + + return ret; +} + +static void ubase_ubus_uninit(struct ub_entity *ue) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + if (udev->hw.io_base.addr) { + ub_iounmap(udev->hw.io_base.addr); + udev->hw.io_base.addr = NULL; + } + + if (udev->hw.mem_base.addr) { + devm_iounmap(&ue->dev, udev->hw.mem_base.addr); + udev->hw.mem_base.addr = NULL; + } + + if (udev->hw.rs0_base.addr) { + ub_iounmap(udev->hw.rs0_base.addr); + udev->hw.rs0_base.addr = NULL; + } + + ub_entity_enable(ue, 0); +} + +static void ubase_port_reset_prepare(struct ub_entity *ue, u16 port_id) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_info(udev, "port %u reset prepare.\n", port_id); + ubase_port_down(udev); +} + +static void ubase_port_reset_done(struct ub_entity *ue, u16 port_id) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_port_up(udev); + ubase_info(udev, "port %u reset done.\n", port_id); + udev->reset_stat.port_reset_cnt++; +} + +static struct ub_share_port_ops ubase_share_port_ops = { + .reset_prepare = ubase_port_reset_prepare, + .reset_done = ubase_port_reset_done +}; + +static int ubase_ubus_reg_share_port(struct ubase_dev *udev) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + struct ubase_caps *caps = &udev->caps.dev_caps; + int ret; + + if (!ubase_dev_ubl_supported(udev)) + return 0; + + ret = ub_register_share_port(ue, caps->ub_port_logic_id, + &ubase_share_port_ops); + if (ret) + ubase_err(udev, + "failed to register share logical port %u, ret = %d.\n", + caps->ub_port_logic_id, ret); + + return ret; +} + +static void ubase_ubus_unreg_share_port(struct ubase_dev *udev) +{ + struct ub_entity *ue = container_of(udev->dev, struct ub_entity, dev); + struct ubase_caps *caps = &udev->caps.dev_caps; + + if (!ubase_dev_ubl_supported(udev)) + return; + + ub_unregister_share_port(ue, caps->ub_port_logic_id, + &ubase_share_port_ops); +} + +/* ubase_ubus_probe - Device initialization routine + * @ue: ub entity information struct + * @utbl_entry: entry in ubase_ubus_tbl + * + * ubase_ubus_probe initializes an UE identified by an ub_entity structure. + * + * Returns 0 on success, negative on failure + */ +static int ubase_ubus_probe(struct ub_entity *ue, + const struct ub_device_id *utbl_entry) +{ + struct ubase_dev *udev; + int ret; + + ub_set_user_info(ue); + + udev = devm_kzalloc(&ue->dev, sizeof(*udev), GFP_KERNEL); + if (!udev) { + dev_err(&ue->dev, "failed to alloc ubase dev.\n"); + return -ENOMEM; + } + + udev->dev = &ue->dev; + udev->dev_id = ubase_adev_idx_alloc(); + if (udev->dev_id < 0) { + ubase_err(udev, + "failed to alloc dev id(%d).\n", udev->dev_id); + devm_kfree(&ue->dev, udev); + return udev->dev_id; + } + + udev->caps.dev_caps.tid = ue->tid; + udev->caps.dev_caps.eid = ue->eid; + udev->caps.dev_caps.upi = ue->upi; + udev->caps.dev_caps.ctl_no = ue->ubc->ctl_no; + + dev_set_drvdata(&ue->dev, udev); + + ret = ubase_ubus_init(ue); + if (ret) { + ubase_err(udev, + "failed to init ubus, ret = %d.\n", ret); + goto err_ubus_init; + } + + ret = ubase_dev_init(udev); + if (ret) { + ubase_err(udev, + "failed to init ubase dev, ret = %d.\n", ret); + goto err_udev_init; + } + + ret = ubase_ubus_reg_share_port(udev); + if (ret) + goto err_register_share_port; + + return 0; + +err_register_share_port: + ubase_dev_uninit(udev); +err_udev_init: + ubase_ubus_uninit(ue); +err_ubus_init: + dev_set_drvdata(&ue->dev, NULL); + ubase_adev_idx_free(udev->dev_id); + devm_kfree(&ue->dev, udev); + ub_unset_user_info(ue); + + return ret; +} + +static void __ubase_ubus_remove(struct ub_entity *ue) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_ubus_unreg_share_port(udev); + ubase_dev_uninit(udev); + ubase_ubus_uninit(ue); + ub_unset_user_info(ue); + ubase_adev_idx_free(udev->dev_id); + dev_set_drvdata(&ue->dev, NULL); + devm_kfree(&ue->dev, udev); +} + +/* ubase_remove - Device removal routine + * @ue: ub entity information struct + */ +static void ubase_ubus_remove(struct ub_entity *ue) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + while (test_and_set_bit(UBASE_STATE_DISABLED_B, &udev->state_bits)) + msleep(UBASE_RST_WAIT_TIME); + + ub_disable_entities(ue); + __ubase_ubus_remove(ue); +} + +static void ubase_ubus_shutdown(struct ub_entity *ue) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + while (test_and_set_bit(UBASE_STATE_DISABLED_B, &udev->state_bits)) + msleep(UBASE_RST_WAIT_TIME); + + ubase_dbg(udev, "ubase_shutdown start.\n"); + + __ubase_ubus_remove(ue); +} + +int ubase_ubus_irq_vectors_alloc(struct device *dev) +{ + struct ub_entity *ue = container_of(dev, struct ub_entity, dev); + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + int irqs_num; + + udev->irq_table.irqs_num = udev->caps.dev_caps.num_aeq_vectors + + udev->caps.dev_caps.num_ceq_vectors + + udev->caps.dev_caps.num_misc_vectors; + + irqs_num = ub_alloc_irq_vectors(ue, UBASE_MIN_IRQ_NUM, + udev->irq_table.irqs_num); + if (irqs_num == -ENOSPC) { + ubase_err(udev, + "bus is unable to provide sufficient number of interrupts.\n"); + goto out; + } + + if (irqs_num < 0) { + ubase_err(udev, + "failed to allocate USI vectors, irqs_num = %d.\n", + irqs_num); + goto out; + } + + if ((u32)irqs_num < udev->irq_table.irqs_num) { + ubase_warn(udev, + "need to allocate %u USI resource, but only allocated %d.\n", + udev->irq_table.irqs_num, irqs_num); + udev->irq_table.irqs_num = (u32)irqs_num; + } + + return 0; + +out: + ub_disable_intr(ue); + return -EFAULT; +} + +void ubase_ubus_irq_vectors_free(struct device *dev) +{ + struct ub_entity *ue = container_of(dev, struct ub_entity, dev); + + ub_disable_intr(ue); +} + +int ubase_ubus_irq_vector(struct device *dev, u32 idx) +{ + struct ub_entity *ue = container_of(dev, struct ub_entity, dev); + + return ub_irq_vector(ue, idx); +} + +static int ubase_ubus_suspend(struct device *dev) +{ + struct ubase_dev *udev = dev_get_drvdata(dev); + + ubase_info(udev, "UBUS suspend start.\n"); + ubase_suspend(udev); + + return 0; +} + +static int ubase_ubus_resume(struct device *dev) +{ + struct ubase_dev *udev = dev_get_drvdata(dev); + + ubase_info(udev, "UBUS resume start.\n"); + ubase_resume(udev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ubase_ubus_pm_ops, ubase_ubus_suspend, ubase_ubus_resume); + +static int ubase_ubus_virt_configure(struct ub_entity *ue, int bus_ue_id, bool is_en) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + int ret; + + /* + * The ubus framework have ensure that only mue can come + * here, so we not need to check is this a mue again. + */ + ubase_info(udev, "ubase virt configure set idx = %d en = %d.\n", + bus_ue_id, is_en); + + if (!is_en) + ret = ub_disable_ue(ue, bus_ue_id); + else + ret = ub_enable_ue(ue, bus_ue_id); + + return ret; +} + +static int ubase_ubus_virt_notify(struct ub_entity *ue, int bus_ue_id, bool is_en) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_info(udev, "ubase virt notify, ue id = %d, en = %d.\n", + bus_ue_id, is_en); + + ubase_virt_handler(udev, (u16)bus_ue_id, is_en); + + return 0; +} + +static int ubase_ubus_activate(struct ub_entity *ue, u32 bus_ue_id) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_info(udev, "ubase activate ue id = %u.\n", bus_ue_id); + + return ubase_activate_handler(udev, bus_ue_id); +} + +static int ubase_ubus_deactivate(struct ub_entity *ue, u32 bus_ue_id) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_info(udev, "ubase deactivate ue id = %u.\n", bus_ue_id); + + return ubase_deactivate_handler(udev, bus_ue_id); +} + +static void ubase_ubus_reset_prepare(struct ub_entity *ue) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_info(udev, "UBUS ELR start.\n"); + ubase_suspend(udev); +} + +static void ubase_ubus_reset_done(struct ub_entity *ue) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_resume(udev); + ubase_info(udev, "UBUS ELR done.\n"); +} + +static ub_ers_result_t ubase_ubus_error_detected(struct ub_entity *ue, + ub_channel_state_t state) +{ + struct ubase_dev *udev = dev_get_drvdata(&ue->dev); + + ubase_info(udev, "UBUS error detected, state = %u.\n", state); + + switch (state) { + case ub_channel_io_normal: + return UB_ERS_RESULT_NEED_RESET; + case ub_channel_io_frozen: + return UB_ERS_RESULT_DISCONNECT; + case ub_channel_io_perm_failure: + default: + return UB_ERS_RESULT_NONE; + } +} + +static const struct ub_error_handlers ubase_ubus_err_handler = { + .ub_reset_prepare = ubase_ubus_reset_prepare, + .ub_reset_done = ubase_ubus_reset_done, + .ub_error_detected = ubase_ubus_error_detected, +}; + +static struct ub_driver ubase_ubus_driver = { + .name = ubase_ubus_driver_name, + .id_table = ubase_ubus_tbl, + .probe = ubase_ubus_probe, + .remove = ubase_ubus_remove, + .shutdown = ubase_ubus_shutdown, + .virt_configure = ubase_ubus_virt_configure, + .virt_notify = ubase_ubus_virt_notify, + .err_handler = &ubase_ubus_err_handler, + .activate = ubase_ubus_activate, + .deactivate = ubase_ubus_deactivate, + .driver = { + .pm = &ubase_ubus_pm_ops, + }, +}; + +int ubase_ubus_register_driver(void) +{ + return ub_register_driver(&ubase_ubus_driver); +} + +void ubase_ubus_unregister_driver(void) +{ + ub_unregister_driver(&ubase_ubus_driver); +} + +int ubase_ubus_reset_entry(struct device *dev) +{ + struct ub_entity *ue = container_of(dev, struct ub_entity, dev); + struct ubase_dev *udev = dev_get_drvdata(dev); + int ret; + + ret = ub_reset_entity(ue); + if (ret) + ubase_err(udev, "failed to trigger reset, ret = %d.\n", ret); + + return ret; +} + +void ubase_ubus_reinit(struct device *dev) +{ + struct ub_entity *ue = container_of(dev, struct ub_entity, dev); + + ub_set_user_info(ue); + ub_entity_enable(ue, 1); +} diff --git a/drivers/ub/ubase/ubase_ubus.h b/drivers/ub/ubase/ubase_ubus.h new file mode 100644 index 0000000000000000000000000000000000000000..3e85fe7bfaea42feff468cd787f2a0bfbf506ba1 --- /dev/null +++ b/drivers/ub/ubase/ubase_ubus.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef __UBASE_UBUS_H__ +#define __UBASE_UBUS_H__ + +#include + +#define UBASE_UBUS_IO_RESOURCE 2 +#define UBASE_UBUS_MEM_RESOURCE 1 +#define UBASE_UBUS_RESOURCE_0 0 + +#define UBASE_VENDOR_ID 0xCC08 + +#define UBASE_DEV_ID_K_0_URMA_MUE 0xA001 +#define UBASE_DEV_ID_K_0_URMA_UE 0xA002 +#define UBASE_DEV_ID_K_0_CDMA_MUE 0xA003 +#define UBASE_DEV_ID_K_0_CDMA_UE 0xA004 +#define UBASE_DEV_ID_K_0_PMU_MUE 0xA005 +#define UBASE_DEV_ID_K_0_PMU_UE 0xA006 + +#define UBASE_DEV_ID_A_0_URMA_MUE 0xD802 +#define UBASE_DEV_ID_A_0_URMA_UE 0xD803 +#define UBASE_DEV_ID_A_0_CDMA_MUE 0xD804 +#define UBASE_DEV_ID_A_0_CDMA_UE 0xD805 +#define UBASE_DEV_ID_A_0_PMU_MUE 0xD806 +#define UBASE_DEV_ID_A_0_PMU_UE 0xD807 +#define UBASE_DEV_ID_A_0_UBOE_MUE 0xD80B +#define UBASE_DEV_ID_A_0_UBOE_UE 0xD80C + +struct ubase_bus_eid; + +int ubase_ubus_register_driver(void); +void ubase_ubus_unregister_driver(void); + +int ubase_ubus_irq_vectors_alloc(struct device *dev); +void ubase_ubus_irq_vectors_free(struct device *dev); +int ubase_ubus_irq_vector(struct device *dev, u32 idx); + +int ubase_ubus_reset_entry(struct device *dev); +void ubase_ubus_reinit(struct device *dev); + +#endif diff --git a/drivers/ub/ubfi/Kconfig b/drivers/ub/ubfi/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..d3889afb24521a62dc20c178c4799592f9e3af4e --- /dev/null +++ b/drivers/ub/ubfi/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# UnifiedBus firmware interface driver configuration +# + +if UB_UBUS + +config UB_UBFI + tristate "UnifiedBus firmware interface driver" + depends on UB_UBUS + default n + help + This option support for UnifiedBus firmware interface driver. + If you have ub function say y and it will be accessible from + within Linux. To compile this driver as a module, choose M here. + Say 'M' here unless you know what you are doing + +config UB_UBRT_PLAT_DEV + bool "Enable UBRT platform device support" + depends on UB_UBUS + default n + help + This option enables the configuration of platform devices related to + the ub ubrt table. + If enabled, the UBRT-related platform device will obtain the + interrupt ID from the ubrt table instead of the IORT table. + The obtained interrupt ID will be used for the MSI interrupt of the + UBRT-related platform device. + +endif diff --git a/drivers/ub/ubfi/Makefile b/drivers/ub/ubfi/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..1fa9bb71b43df407325a67aad29e1345c417b8f4 --- /dev/null +++ b/drivers/ub/ubfi/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-y += irq.o ubrt-fwnode.o + +ubfi-y := ub_fi.o ubrt.o ubc.o ummu.o resv_mem.o +obj-$(CONFIG_UB_UBFI) += ubfi.o diff --git a/drivers/ub/ubfi/irq.c b/drivers/ub/ubfi/irq.c new file mode 100644 index 0000000000000000000000000000000000000000..5835bc8421b367cb34c6ed58f6e636277267e581 --- /dev/null +++ b/drivers/ub/ubfi/irq.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include +#include +#include +#include + +int ub_update_msi_domain(struct device *dev, + enum irq_domain_bus_token bus_token) +{ +#ifdef CONFIG_GENERIC_MSI_IRQ + struct fwnode_handle *fwnode; + struct irq_domain *domain; + + domain = dev->msi.domain; + if (!domain) { + dev_err(dev, "find base irq domain failed!\n"); + return -ENODEV; + } + + fwnode = domain->fwnode; + if (!fwnode) { + dev_err(dev, "find fwnode failed!\n"); + return -ENODEV; + } + + domain = irq_find_matching_fwnode(fwnode, bus_token); + if (!domain) { + dev_err(dev, "find irq domain failed!\n"); + return -ENODEV; + } + + /* Update msi domain with new bus_token */ + dev_set_msi_domain(dev, domain); +#endif + return 0; +} +EXPORT_SYMBOL_GPL(ub_update_msi_domain); + +int ubrt_register_gsi(u32 hwirq, int trigger, int polarity, const char *name, + struct resource *res) +{ +#ifdef CONFIG_ACPI + int irq; + + if (!res) { + pr_err("ub register gsi, res is null\n"); + return -EINVAL; + } + + irq = acpi_register_gsi(NULL, hwirq, trigger, polarity); + if (irq <= 0) { + pr_err("could not register ub gsi hwirq %u\n", hwirq); + return -EINVAL; + } + + res->name = name; + res->start = irq; + res->end = irq; + res->flags = IORESOURCE_IRQ; + return 0; +#else + return -EINVAL; +#endif +} +EXPORT_SYMBOL_GPL(ubrt_register_gsi); + +void ubrt_unregister_gsi(u32 hwirq) +{ +#ifdef CONFIG_ACPI + acpi_unregister_gsi(hwirq); +#endif +} +EXPORT_SYMBOL_GPL(ubrt_unregister_gsi); + +#if IS_ENABLED(CONFIG_UB_UBRT_PLAT_DEV) +int ubrt_pmsi_get_interrupt_id(struct device *dev, u32 *interrupt_id) +{ + struct ubrt_fwnode *fw; + struct ummu_node *node; + + if (!dev->fwnode) + return -EINVAL; + + fw = ubrt_fwnode_get(dev->fwnode); + if (!fw) + return -ENODEV; + + switch (fw->type) { + case UBRT_UMMU: + node = (struct ummu_node *)fw->ubrt_node; + *interrupt_id = node->intr_id; + break; + case UBRT_UMMU_PMU: + node = (struct ummu_node *)fw->ubrt_node; + *interrupt_id = node->pmu_intr_id; + break; + default: + return -ENODEV; + } + dev_info(dev, "ubct pmsi successfully obtained interrupt id[0x%x].\n", *interrupt_id); + return 0; +} +#endif diff --git a/drivers/ub/ubfi/resv_mem.c b/drivers/ub/ubfi/resv_mem.c new file mode 100644 index 0000000000000000000000000000000000000000..ac9cc0f7da3cff8e13c3106f059b58dc9d53e9cd --- /dev/null +++ b/drivers/ub/ubfi/resv_mem.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + * Description: + * This file implements the parsing of the ub reserved memory node in the UBRT table. + */ + +#include +#include +#include +#include +#include +#include + +#include "ubrt.h" +#include "ub_fi.h" +#include "ubc.h" + +#define UBRT_RMR_DIRECT_REMAP BIT(0) + +struct ubrt_resv_mem_range { + u8 flags; + u8 reserved[7]; + u64 memory_base; + u64 memory_size; +}; + +struct ubrt_resv_mem_node { + struct ub_table_header header; + u16 count; + u8 reserved[6]; + u8 ranges[]; __counted_by(count) +}; + +static void parse_ummu_reserved_memory(void *info_node, struct list_head *list) +{ + struct ubrt_resv_mem_node *sub_table = info_node; + struct ubrt_resv_mem_range *mem_range; + struct iommu_resv_region *region; + int prot = IOMMU_READ | IOMMU_WRITE; + u32 i; + + if (!sub_table->count) { + pr_warn("resv mem table has no node.\n"); + return; + } + + mem_range = (struct ubrt_resv_mem_range *)sub_table->ranges; + for (i = 0; i < sub_table->count; i++) { + if (!(mem_range[i].flags & UBRT_RMR_DIRECT_REMAP)) + continue; + + prot |= IOMMU_MMIO; + region = iommu_alloc_resv_region(mem_range[i].memory_base, mem_range[i].memory_size, + prot, IOMMU_RESV_DIRECT, GFP_KERNEL); + if (!region) { + pr_err("Failed to allocate resv region for ummu.\n"); + return; + } + + list_add_tail(®ion->list, list); + } +} + +static void reserved_memory_parse(u64 pointer, struct list_head *list) +{ + void *info_node = ub_table_get(pointer); + + if (!info_node) + return; + + parse_ummu_reserved_memory(info_node, list); + ub_table_put(info_node); +} + +void ubrt_iommu_get_resv_regions(struct device *dev, struct list_head *list) +{ + struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); + struct ubrt_sub_table *sub_table; + u32 i; + + if (!fwspec || !acpi_table) + return; + + for (i = 0; i < acpi_table->count; i++) { + sub_table = &acpi_table->sub_table[i]; + if (sub_table->type == UB_RESERVED_MEMORY_TABLE) + reserved_memory_parse(sub_table->pointer, list); + } +} +EXPORT_SYMBOL_GPL(ubrt_iommu_get_resv_regions); diff --git a/drivers/ub/ubfi/ub_fi.c b/drivers/ub/ubfi/ub_fi.c new file mode 100644 index 0000000000000000000000000000000000000000..50b359e52b7b05a46fc368b4bba0040c355a44e6 --- /dev/null +++ b/drivers/ub/ubfi/ub_fi.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubfi driver: " fmt + +#include +#include +#include + +#include "ubrt.h" +#include "ub_fi.h" + +#define ACPI_SIG_UBRT "UBRT" /* UB Root Table */ +#define UBIOS_INFO_TABLE "linux,ubios-information-table" + +enum bios_report_mode bios_mode = UNKNOWN; + +static void ub_bios_mode_init(void) +{ + if (acpi_disabled) + bios_mode = DTS; + else + bios_mode = ACPI; + + pr_info("Starting with mode: %d\n", bios_mode); +} + +static int ubfi_get_acpi_ubrt(void) +{ + struct acpi_table_header *header; + acpi_status status; + + status = acpi_get_table(ACPI_SIG_UBRT, 0, &header); + if (ACPI_FAILURE(status)) { + pr_err("ACPI failed to get UBRT.\n"); + if (status != AE_NOT_FOUND) + pr_err("ACPI failed msg: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } + acpi_table = (struct acpi_table_ubrt *)header; + pr_info("get ubrt by acpi success\n"); + return 0; +} + +static int ubfi_get_dts_ubrt(void) +{ + struct device_node *node; + u64 phys_addr; + + node = of_find_node_by_path("/chosen"); + if (!node) { + pr_err("Failed to get device tree chosen node\n"); + return -EINVAL; + } + + if (of_property_read_u64(node, UBIOS_INFO_TABLE, &phys_addr)) { + pr_err("Failed to get %s node\n", UBIOS_INFO_TABLE); + return -EINVAL; + } + + ubios_table = (struct ubios_root_table *)ub_table_get(phys_addr); + if (!ubios_table) + return -ENOMEM; + + pr_info("ubfi get ubrt by device tree success\n"); + return 0; +} + +static int ubfi_get_ubrt(void) +{ + if (bios_mode == ACPI) + return ubfi_get_acpi_ubrt(); + else if (bios_mode == DTS) + return ubfi_get_dts_ubrt(); + return -EINVAL; +} + +static int handle_ubrt(void) +{ + if (bios_mode == ACPI) + return handle_acpi_ubrt(); + else if (bios_mode == DTS) + return handle_dts_ubrt(); + + return -EINVAL; +} + +static void ubfi_put_ubrt(void) +{ + if (bios_mode == ACPI) { + acpi_put_table((struct acpi_table_header *)acpi_table); + acpi_table = NULL; + } else if (bios_mode == DTS) { + ub_table_put(ubios_table); + ubios_table = NULL; + } +} + +static int __init ubfi_init(void) +{ + int ret; + + ub_bios_mode_init(); + + ret = ubfi_get_ubrt(); + if (ret) { + pr_warn("can't get ub information from bios, ret=%d\n", ret); + return 0; + } + + return handle_ubrt(); +} + +static void __exit ubfi_exit(void) +{ + uninit_ub_nodes(); + ubfi_put_ubrt(); +} + +module_init(ubfi_init); +module_exit(ubfi_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("UnifiedBus firmware interface driver"); +MODULE_IMPORT_NS(UB_UBFI); diff --git a/drivers/ub/ubfi/ub_fi.h b/drivers/ub/ubfi/ub_fi.h new file mode 100644 index 0000000000000000000000000000000000000000..3edf8dd6de4ec24ef46e3141a5a9303267fe112d --- /dev/null +++ b/drivers/ub/ubfi/ub_fi.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UB_FI_H__ +#define __UB_FI_H__ + +enum bios_report_mode { + ACPI = 0, + DTS = 1, + UBIOS = 3, + UNKNOWN = 4, +}; + +extern enum bios_report_mode bios_mode; +#endif /* __UB_FI_H__ */ diff --git a/drivers/ub/ubfi/ubc.c b/drivers/ub/ubfi/ubc.c new file mode 100644 index 0000000000000000000000000000000000000000..a3f7bab8863f698702a6e963edefb3cbe1bf1868 --- /dev/null +++ b/drivers/ub/ubfi/ubc.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubfi ubc: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ubrt.h" +#include "ub_fi.h" +#include "ubc.h" + +#define UB_MSGQ_INT_TRIGGER_MASK BIT(0) +#define UB_MSGQ_INT_POLARITY_MASK BIT(1) + +#define ACPI_UBC_DEVICE_HID "HISI0541" + +#define to_ub_ubc(n) container_of(n, struct ub_bus_controller, dev) + +LIST_HEAD(ubc_list); +EXPORT_SYMBOL_GPL(ubc_list); + +u32 ubc_eid_start; +EXPORT_SYMBOL_GPL(ubc_eid_start); + +u32 ubc_eid_end; +EXPORT_SYMBOL_GPL(ubc_eid_end); + +u32 ubc_cna_start; +EXPORT_SYMBOL_GPL(ubc_cna_start); + +u32 ubc_cna_end; +EXPORT_SYMBOL_GPL(ubc_cna_end); + +u8 ubc_feature; +EXPORT_SYMBOL_GPL(ubc_feature); + +static bool cluster_mode; + +static acpi_status acpi_processor_ubc(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + struct ub_bus_controller *ubc = context; + struct acpi_device *adev; + unsigned long long uid; + acpi_status status; + struct device *dev; + int ret; + + status = acpi_evaluate_integer(handle, "_UID", NULL, &uid); + if (ACPI_FAILURE(status)) + return AE_CTRL_TERMINATE; + + pr_info("ubc acpi ubc->ctl_no %u, uid: %llu\n", ubc->ctl_no, uid); + if (ubc->ctl_no != (u32)uid) + return AE_OK; + + adev = acpi_get_acpi_dev(handle); + if (!adev) + return AE_CTRL_TERMINATE; + + dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev); + if (!dev) { + status = AE_CTRL_TERMINATE; + goto out; + } + ret = ub_update_msi_domain(dev, DOMAIN_BUS_UB_MSI); + if (ret) { + status = AE_CTRL_TERMINATE; + goto out; + } + dev_set_msi_domain(&ubc->dev, dev->msi.domain); + pr_debug("set ubc msi domain success by acpi\n"); + +out: + acpi_put_acpi_dev(adev); + return status; +} + +static int acpi_update_ubc_msi_domain(void) +{ + struct ub_bus_controller *ubc; + acpi_status status; + + list_for_each_entry(ubc, &ubc_list, node) { + /* Get UB Bus Controller from DSDT */ + status = acpi_get_devices(ACPI_UBC_DEVICE_HID, + acpi_processor_ubc, + ubc, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + } + + return 0; +} + +static struct irq_domain *of_usi_get_domain(struct device_node *np, + enum irq_domain_bus_token token) +{ + struct device_node *usi_np; + struct irq_domain *d; + + usi_np = of_parse_phandle(np, "msi-parent", 0); + if (!usi_np) + return NULL; + + d = irq_find_matching_host(usi_np, token); + if (!d) + of_node_put(usi_np); + + return d; +} + +static int dts_update_ubc_msi_domain(void) +{ + struct ub_bus_controller *ubc; + struct device_node *np; + struct irq_domain *d; + u32 ctl_no; + bool find; + int ret; + + for_each_compatible_node(np, NULL, "ub,ubc") { + ret = of_property_read_u32(np, "index", &ctl_no); + if (ret) { + pr_err("dts can't find ubc index\n"); + continue; + } + + find = false; + list_for_each_entry(ubc, &ubc_list, node) { + if (ubc->ctl_no == ctl_no) { + find = true; + break; + } + } + if (!find) { + pr_err("can't find ubc no=%u\n", ctl_no); + continue; + } + + d = of_usi_get_domain(np, DOMAIN_BUS_UB_MSI); + if (!d) { + pr_err("can't find ub irq domain\n"); + continue; + } + + dev_set_msi_domain(&ubc->dev, d); + pr_debug("set ubc[%u] msi domain success\n", ctl_no); + } + return 0; +} + +static int parse_ubc_info(struct ubc_node *node, struct ub_bus_controller *ubc) +{ + ubc->attr.int_id_start = node->int_id_start; + ubc->attr.int_id_end = node->int_id_end; + ubc->attr.hpa_base = node->hpa_base; + ubc->attr.hpa_size = node->hpa_size; + ubc->attr.mem_size_limit = node->mem_size_limit; + ubc->attr.dma_cca = node->dma_cca; + ubc->attr.ummu_map = node->ummu_mapping; + ubc->attr.proximity_domain = node->proximity_domain; + ubc->attr.queue_addr = node->msg_queue_base; + ubc->attr.queue_size = node->msg_queue_size; + ubc->attr.queue_depth = node->msg_queue_depth; + ubc->attr.msg_int = node->msg_int; + ubc->attr.msg_int_attr = node->msg_int_attr; + ubc->attr.ubc_guid_low = node->ubc_guid_low; + ubc->attr.ubc_guid_high = node->ubc_guid_high; + pr_info("ubc interrupt id[0x%x-0x%x], ubc_guid[%llx-%llx], msg_int[0x%x]\n", + ubc->attr.int_id_start, ubc->attr.int_id_end, + ubc->attr.ubc_guid_high, ubc->attr.ubc_guid_low, + ubc->attr.msg_int); + + ubc->data = kzalloc(UBC_VENDOR_INFO_SIZE, GFP_KERNEL); + if (!ubc->data) + return -ENOMEM; + memcpy(ubc->data, node->vendor_info, UBC_VENDOR_INFO_SIZE); + + ubc->cluster = cluster_mode; + pr_info("ubc real cluster mode is %d\n", ubc->cluster); + INIT_LIST_HEAD(&ubc->resources); + (void)snprintf(ubc->name, sizeof(ubc->name), "UB_BUS_CTL%u", ubc->ctl_no); + pr_info("create ubc success, ubc name=%s\n", ubc->name); + return 0; +} + +static int ubc_dev_new_resource_entry(struct resource *res, + struct list_head *resources) +{ + struct resource_entry *rentry; + + rentry = resource_list_create_entry(NULL, 0); + if (!rentry) + return -ENOMEM; + + *rentry->res = *res; + rentry->offset = 0; + resource_list_add_tail(rentry, resources); + return 0; +} + +static int dts_register_irq(u32 ctl_no, int irq_type, const char *name, + struct resource *res) +{ + struct device_node *np; + u32 irq = 0, index; + int ret; + + for_each_compatible_node(np, NULL, "ub,ubc") { + ret = of_property_read_u32(np, "index", &index); + if (ret) { + pr_err("dts can't find ubc index\n"); + continue; + } + if (ctl_no != index) + continue; + + irq = irq_of_parse_and_map(np, irq_type); + if (!irq) + continue; + } + + if (!irq) { + pr_err("irq_type %d parse and map fail\n", irq_type); + return -EINVAL; + } + pr_info("irq_type[%d] register success, irq=%u\n", irq_type, irq); + + res->name = name; + res->start = irq; + res->end = irq; + res->flags = IORESOURCE_IRQ; + return 0; +} + +static void remove_ubc_resource(struct ub_bus_controller *ubc) +{ + struct resource_entry *entry; + struct resource *res; + + resource_list_for_each_entry(entry, &ubc->resources) { + res = entry->res; + if (res->parent && (res->flags & IORESOURCE_MEM)) + release_resource(res); + if ((res->flags & IORESOURCE_IRQ) + && !strcmp(res->name, "UBUS") + && !ubc->ctl_no) { + if (bios_mode == ACPI) + ubrt_unregister_gsi(ubc->attr.msg_int); + else if (bios_mode == DTS) + irq_dispose_mapping(ubc->queue_virq); + } + } + + resource_list_free(&ubc->resources); +} + +static int add_ubc_mmio_resource(struct ubc_node *node, + struct ub_bus_controller *ubc) +{ + struct resource res = {}; + int ret; + + res.start = node->hpa_base; + res.end = node->hpa_base + node->hpa_size - 1; + res.flags = IORESOURCE_MEM; + res.name = ubc->name; + + ret = ubc_dev_new_resource_entry(&res, &ubc->resources); + if (!ret) + pr_info("add %s resource success, %pR\n", res.name, &res); + + return ret; +} + +static int add_ubc_irq_resource(struct ubc_node *node, + struct ub_bus_controller *ubc) +{ + int hwirq, trigger, polarity, ret; + struct resource res = {}; + + hwirq = ubc->attr.msg_int; + trigger = !!(ubc->attr.msg_int_attr & UB_MSGQ_INT_TRIGGER_MASK); + polarity = !!(ubc->attr.msg_int_attr & UB_MSGQ_INT_POLARITY_MASK); + + if (bios_mode == ACPI) + ret = ubrt_register_gsi(hwirq, trigger, polarity, "UBUS", &res); + else if (bios_mode == DTS) + ret = dts_register_irq(ubc->ctl_no, 0, "UBUS", &res); + else + ret = -EINVAL; + + if (ret) { + pr_err("register irq fail, ret=%d\n", ret); + return ret; + } + + ret = ubc_dev_new_resource_entry(&res, &ubc->resources); + if (ret) + goto out; + + ubc->queue_virq = res.start; + + pr_info("ubc msgq irq register success\n"); + return 0; + +out: + if (bios_mode == ACPI) + ubrt_unregister_gsi(hwirq); + else if (bios_mode == DTS) + irq_dispose_mapping(res.start); + + return ret; +} + +static void ub_release_ubc_dev(struct device *dev) +{ + struct ub_bus_controller *ubc = to_ub_ubc(dev); + struct device_node *usi_np; + + pr_info("%s release ub bus controller device.\n", ubc->name); + + if (bios_mode == DTS) { + usi_np = irq_domain_get_of_node(dev->msi.domain); + if (usi_np) + of_node_put(usi_np); + } + + remove_ubc_resource(ubc); + kfree(ubc->data); + list_del(&ubc->node); + kfree(ubc); +} + +static int init_ubc(struct ub_bus_controller *ubc) +{ + struct device *dev = &ubc->dev; + int ret; + + INIT_LIST_HEAD(&ubc->devs); + device_initialize(dev); + set_dev_node(dev, pxm_to_node(ubc->attr.proximity_domain)); + + dev->release = ub_release_ubc_dev; + dev->coherent_dma_mask = GENMASK_ULL(31, 0); + dev_set_name(dev, "ub_bus_controller%u", ubc->ctl_no); + + ret = device_add(dev); + if (ret) { + pr_err("Add ub bus controller device failed.\n"); + put_device(&ubc->dev); + } + + return ret; +} + +static void ubc_validate_resources(struct list_head *resources, + unsigned long type) +{ + struct resource_entry *tmp, *entry, *entry2; + struct resource *res1, *res2, *root = NULL; + LIST_HEAD(list); + + WARN_ON((type & IORESOURCE_MEM) == 0); + root = &iomem_resource; + + list_splice_init(resources, &list); + resource_list_for_each_entry_safe(entry, tmp, &list) { + bool can_free = false; + resource_size_t end; + + res1 = entry->res; + if (!(type & res1->flags)) + goto next; + + end = min(res1->end, root->end); + if (end <= res1->start) { + pr_info("ub bus controller resources %pR (ignored, not CPU addressable)\n", + res1); + can_free = true; + goto next; + } else if (res1->end != end) { + pr_info("ub bus controller resources %pR ([%#llx-%#llx] ignored, not CPU addressable)\n", + res1, (unsigned long long)end + 1, + (unsigned long long)res1->end); + res1->end = end; + } + + resource_list_for_each_entry(entry2, resources) { + res2 = entry2->res; + if (!(type & res2->flags)) + continue; + + if (resource_overlaps(res1, res2)) { + res2->start = min(res1->start, res2->start); + res2->end = max(res1->end, res2->end); + pr_warn("ub bus controller resources expanded to %pR; %pR ignored\n", + res2, res1); + can_free = true; + goto next; + } + } + +next: + resource_list_del(entry); + if (can_free) + resource_list_free_entry(entry); + else + resource_list_add_tail(entry, resources); + } +} + +static void ubc_device_add_resources(struct list_head *resources) +{ + struct resource *res, *root = NULL; + struct resource_entry *entry, *tmp; + int ret; + + resource_list_for_each_entry_safe(entry, tmp, resources) { + res = entry->res; + if (!(res->flags & IORESOURCE_MEM)) + continue; + + root = &iomem_resource; + if (res == root) + continue; + + ret = insert_resource(root, res); + if (ret) { + pr_warn("conflict, ignoring controller resources %pR\n", + res); + resource_list_destroy_entry(entry); + } + } +} + +static void ubc_declare_resources(struct ub_bus_controller *ubc) +{ + struct resource_entry *window; + resource_size_t offset; + char addr[SZ_64], *fmt; + struct resource *res; + + /* Show ub bus controller's resources */ + resource_list_for_each_entry(window, &ubc->resources) { + offset = window->offset; + res = window->res; + if (offset) { + fmt = " (bus address [0x%#010llx-0x%#010llx])"; + snprintf(addr, sizeof(addr), fmt, + (unsigned long long)(res->start - offset), + (unsigned long long)(res->end - offset)); + } else { + addr[0] = '\0'; + } + + pr_info("ubc resource %pR%s\n", res, addr); + } +} + +static void ubc_check_and_add_resources(struct ub_bus_controller *ubc) +{ + struct resource_entry *entry, *tmp; + + resource_list_for_each_entry_safe(entry, tmp, &ubc->resources) + if (entry->res->flags & IORESOURCE_DISABLED) + resource_list_destroy_entry(entry); + + ubc_validate_resources(&ubc->resources, IORESOURCE_MEM); + + /* Insert resources into kernel */ + ubc_device_add_resources(&ubc->resources); + + ubc_declare_resources(ubc); +} + +static int create_ubc(struct ubc_node *node, u32 ctl_no) +{ + struct ub_bus_controller *ubc; + int ret; + + ubc = kzalloc(sizeof(*ubc), GFP_KERNEL); + if (!ubc) + return -ENOMEM; + ubc->ctl_no = ctl_no; + + ret = parse_ubc_info(node, ubc); + if (ret) + goto free_ubc; + + ret = add_ubc_mmio_resource(node, ubc); + if (ret) + goto free_vendor; + + ret = add_ubc_irq_resource(node, ubc); + if (ret) + goto free_resource; + + /* after init_ubc, ubc resources will be released in the dev->release */ + ret = init_ubc(ubc); + if (ret) + return ret; + + ubc_check_and_add_resources(ubc); + list_add_tail(&ubc->node, &ubc_list); + + return 0; + +free_resource: + remove_ubc_resource(ubc); +free_vendor: + kfree(ubc->data); +free_ubc: + kfree(ubc); + return ret; +} + +static int parse_ubc_table(void *info_node) +{ + struct ubrt_ubc_table *ubc_table = info_node; + struct ubc_node *node = ubc_table->ubcs; + u32 count, i; + int ret; + + count = ubc_table->ubc_count; + if (!count) { + pr_warn("ubc table has no ubc.\n"); + return 0; + } + + /* get ubc common attribute */ + ubc_cna_start = ubc_table->cna_start; + ubc_cna_end = ubc_table->cna_end; + ubc_eid_start = ubc_table->eid_start; + ubc_eid_end = ubc_table->eid_end; + ubc_feature = ubc_table->feature; + cluster_mode = ubc_table->cluster_mode; + + pr_info("cna_start=%u, cna_end=%u\n", ubc_cna_start, ubc_cna_end); + pr_info("eid_start=%u, eid_end=%u\n", ubc_eid_start, ubc_eid_end); + pr_info("ubc_count=%u, bios_cluster_mode=%u, feature=%u\n", count, + cluster_mode, ubc_feature); + if (ubc_cna_start > ubc_cna_end || ubc_eid_start > ubc_eid_end) { + pr_err("eid or cna range is incorrect\n"); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + ret = create_ubc(node, i); + if (ret) { + pr_err("Create No.%u ubc failed, ret=%d\n", i, ret); + return ret; + } + node++; + } + + return 0; +} + +static void ub_destroy_bus_controllers(void) +{ + struct ub_bus_controller *ubc, *tmp; + + list_for_each_entry_safe_reverse(ubc, tmp, &ubc_list, node) + device_unregister(&ubc->dev); + + pr_info("ubc destroy success\n"); +} + +void destroy_ubc(void) +{ + ub_destroy_bus_controllers(); +} + +int handle_ubc_table(u64 pointer) +{ + void *info_node = ub_table_get(pointer); + int ret; + + if (!info_node) + return -EINVAL; + + ret = parse_ubc_table(info_node); + if (ret) + goto err_handle; + + pr_info("Update msi domain for ub bus controller\n"); + /* Update msi domain for ub bus controller */ + if (bios_mode == ACPI) + ret = acpi_update_ubc_msi_domain(); + else + ret = dts_update_ubc_msi_domain(); + + if (ret) + goto err_handle; + + ub_table_put(info_node); + return 0; + +err_handle: + ub_table_put(info_node); + destroy_ubc(); + return ret; +} diff --git a/drivers/ub/ubfi/ubc.h b/drivers/ub/ubfi/ubc.h new file mode 100644 index 0000000000000000000000000000000000000000..966c1a1528106e83ad5f236054d91e3911e66ee2 --- /dev/null +++ b/drivers/ub/ubfi/ubc.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UBC_H__ +#define __UBC_H__ + +#include "ubrt.h" + +#define UBC_VENDOR_INFO_SIZE 256 + +/** + * struct ubc_node - Information about a single UBC(UB Controller) + * @int_id_start: Start value of the assignable Interrupt ID range + * @int_id_end: End value (inclusive) of the range of assignable Interrupt IDs + * @hpa_base: Available Host Physical Address Space Base Address + * @hpa_size: Size of available Host Physical Address space for allocation + * @mem_size_limit: Maximum addressable bit width + * @dma_cca: Indicates UBC DMA CCA (Cache Coherent Attribute) capability + * 0: Supports DMA but does not support CCA + * 1: Supports both DMA and CCA + * Others: Does not support DMA + * @ummu_mapping: Indicates the association between this UBC and UMMU, + * using the UMMU Index, which is represented by the UMMU + * serial number in the UMMU information table. + * @proximity_domain: Indicates the NUMA Domain number to which this UBC belongs. + * @msg_queue_base: UBC MSG Queue Register Base Address + * @msg_queue_size: UBC MSG Queue Register Space Size + * @msg_queue_depth: UBC MSG Queue Interrupt Depth + * @msg_int: UBC MSG Queue Interrupt Number + * @msg_int_attr: UBC MSG Queue Interrupt Attributes + * bit0: Level OR Edge Trigger + * 0: Level + * 1: Edge + * bit1: High level (rising edge) OR Low level (falling edge) + * 0: High level (rising edge) + * 1: Low level (falling edge) + * @vendor_info: Vendor-defined information + */ +struct ubc_node { + u32 int_id_start; + u32 int_id_end; + u64 hpa_base; + u64 hpa_size; + u8 mem_size_limit; + u8 dma_cca; + u16 ummu_mapping; + u16 proximity_domain; + u16 reserved; + u64 msg_queue_base; + u64 msg_queue_size; + u16 msg_queue_depth; + u16 msg_int; + u8 msg_int_attr; + u8 reserved2[59]; + u64 ubc_guid_low; + u64 ubc_guid_high; + u8 vendor_info[UBC_VENDOR_INFO_SIZE]; +}; + +/** + * struct ubrt_ubc_table - all ubcs in ubrt + * @header: Public header of the UBRT table + * @cna_start: Starting CNA number available for this UBPU + * @cna_end: End CNA number available for this UBPU (inclusive) + * @eid_start: Starting EID number available for this UBPU + * @eid_end: End EID number available for this UBPU (inclusive) + * @feature: Features enabled by UBCs: + * bit0: MMIO Token Value, a security feature for MMIO; + * 0 indicates disabled, 1 indicates enabled. + * bit1: MCTP over UB, an MCTP over UB feature; + * 0 indicates disabled, 1 indicates enabled. + * Other bits: reserved. + * @cluster_mode: System Operating Mode + * 0: Standalone mode, the OS can perform UB enumeration. + * 1: Super Node mode, the OS cannot perform active UB enumeration operations. + * @ubc_count: Number of UBCs + */ +struct ubrt_ubc_table { + struct ub_table_header header; + u32 cna_start; + u32 cna_end; + u32 eid_start; + u32 eid_end; + u8 feature; + u8 reserved[3]; + u16 cluster_mode; + u16 ubc_count; + struct ubc_node ubcs[]; +}; + +int handle_ubc_table(u64 pointer); +void destroy_ubc(void); + +#endif /* __UBC_H__ */ diff --git a/drivers/ub/ubfi/ubrt-fwnode.c b/drivers/ub/ubfi/ubrt-fwnode.c new file mode 100644 index 0000000000000000000000000000000000000000..0b0b203b916b673519ddecd416ebe2553dddee3e --- /dev/null +++ b/drivers/ub/ubfi/ubrt-fwnode.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + * Descriptor: Maintain ubrt information acquisition path + */ +#include +#include +#include +#include +#include + +static LIST_HEAD(ubrt_fwnode_list); +static DEFINE_SPINLOCK(ubrt_fwnode_lock); + +int ubrt_fwnode_set(u32 index, enum ubrt_node_type type, struct fwnode_handle *fwnode) +{ + struct ubrt_fwnode *curr; + + if (!fwnode) + return -ENODEV; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry(curr, &ubrt_fwnode_list, list) { + if (curr->type == type && curr->index == index) { + curr->fwnode = fwnode; + return 0; + } + } + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_set); + +struct ubrt_fwnode *ubrt_fwnode_get_by_idx(u32 index, enum ubrt_node_type type) +{ + struct ubrt_fwnode *curr; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry(curr, &ubrt_fwnode_list, list) { + if (curr->type == type && curr->index == index) + return curr; + } + return NULL; +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_get_by_idx); + +struct ubrt_fwnode *ubrt_fwnode_get(struct fwnode_handle *fwnode) +{ + struct ubrt_fwnode *curr; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry(curr, &ubrt_fwnode_list, list) { + if (curr->fwnode == fwnode) + return curr; + + } + return NULL; +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_get); + +u32 ubrt_fwnode_get_count(enum ubrt_node_type type) +{ + struct ubrt_fwnode *curr; + u32 count = 0; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry(curr, &ubrt_fwnode_list, list) { + if (curr->type == type) + count++; + } + return count; +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_get_count); + +int ubrt_fwnode_add(void *node, u32 index, int size, enum ubrt_node_type type) +{ + struct ubrt_fwnode *np; + void *tmp; + + if (!node) + return -EINVAL; + + np = kzalloc(sizeof(*np), GFP_KERNEL); + if (!np) + return -ENOMEM; + + tmp = kmemdup(node, size, GFP_KERNEL); + if (!tmp) { + kfree(np); + return -ENOMEM; + } + + INIT_LIST_HEAD(&np->list); + np->ubrt_node = tmp; + np->index = index; + np->type = type; + guard(spinlock)(&ubrt_fwnode_lock); + list_add_tail(&np->list, &ubrt_fwnode_list); + + return 0; +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_add); + +void ubrt_fwnode_del(u32 index, enum ubrt_node_type type) +{ + struct ubrt_fwnode *curr, *tmp; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry_safe(curr, tmp, &ubrt_fwnode_list, list) { + if (curr->type == type && curr->index == index) { + kfree(curr->ubrt_node); + list_del(&curr->list); + kfree(curr); + break; + } + } +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_del); + +void ubrt_fwnode_del_all(void) +{ + struct ubrt_fwnode *cur_fwnode, *tmp_fwnode; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry_safe(cur_fwnode, tmp_fwnode, &ubrt_fwnode_list, list) { + kfree(cur_fwnode->ubrt_node); + list_del(&cur_fwnode->list); + kfree(cur_fwnode); + } +} +EXPORT_SYMBOL_GPL(ubrt_fwnode_del_all); + +int ubrt_get_interrupt_id(u16 ummu_map, u32 *intr_id) +{ + struct ubrt_fwnode *curr; + struct ummu_node *node; + + guard(spinlock)(&ubrt_fwnode_lock); + list_for_each_entry(curr, &ubrt_fwnode_list, list) { + if (curr->type != UBRT_UMMU || curr->index != ummu_map) + continue; + + node = (struct ummu_node *)curr->ubrt_node; + *intr_id = node->intr_id; + return 0; + } + return -ENXIO; +} +EXPORT_SYMBOL_GPL(ubrt_get_interrupt_id); diff --git a/drivers/ub/ubfi/ubrt.c b/drivers/ub/ubfi/ubrt.c new file mode 100644 index 0000000000000000000000000000000000000000..8908b2ae8eddcc867d9e58e21b8a40663bd391ba --- /dev/null +++ b/drivers/ub/ubfi/ubrt.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubfi ubrt: " fmt + +#include +#include +#include +#include + +#include "ummu.h" +#include "ubc.h" +#include "ubrt.h" + +#define UBIOS_SIG_UBC "ubc" +#define UBIOS_SIG_UMMU "ummu" + +struct acpi_table_ubrt *acpi_table; +struct ubios_root_table *ubios_table; + +/* + * ummu max count is 32, max size is 40 + 32 * 128 = 4640 + * ubc max count is 32, max size is 40 + 88 + 32 * 256 + 32 * 4 = 8448 + */ +#define UBIOS_TABLE_TOTLE_SIZE_MAX 8448 + +/* remember to use ub_table_put to release memory alloced by ub_table_get */ +void *ub_table_get(u64 pa) +{ + void __iomem *va; + u32 total_size; + void *ret; + + if (!pa) + return NULL; + + va = ioremap(pa, sizeof(struct ub_table_header)); + if (!va) { + pr_err("ioremap ub table header failed\n"); + return NULL; + } + + total_size = readl(va + UB_TABLE_HEADER_NAME_LEN); + pr_debug("ub table size is[0x%x]\n", total_size); + if (total_size == 0 || total_size > UBIOS_TABLE_TOTLE_SIZE_MAX) { + pr_err("ubios table size is invalid\n"); + iounmap(va); + return NULL; + } + iounmap(va); + + va = ioremap(pa, total_size); + if (!va) { + pr_err("ioremap full ub table failed\n"); + return NULL; + } + + ret = kzalloc(total_size, GFP_KERNEL); + if (!ret) { + iounmap(va); + return NULL; + } + + memcpy_fromio(ret, va, total_size); + iounmap(va); + return ret; +} + +void ub_table_put(void *va) +{ + kfree(va); +} + +void uninit_ub_nodes(void) +{ + ubrt_fwnode_del_all(); + destroy_ubc(); +} + +int handle_acpi_ubrt(void) +{ + struct ubrt_sub_table *sub_table; + int ret = 0; + u32 i; + + pr_info("acpi ubrt sub table count is %u\n", acpi_table->count); + + for (i = 0; i < acpi_table->count; i++) { + sub_table = &acpi_table->sub_table[i]; + switch (sub_table->type) { + case UB_BUS_CONTROLLER_TABLE: + ret = handle_ubc_table(sub_table->pointer); + break; + case UMMU_TABLE: + ret = handle_ummu_table(sub_table->pointer); + break; + default: + pr_warn("Ignore sub table: type %u\n", sub_table->type); + break; + } + if (ret) { + pr_err("parse ubrt sub table type %u failed\n", + sub_table->type); + goto fail; + } + } + return ret; +fail: + uninit_ub_nodes(); + return ret; +} + +int handle_dts_ubrt(void) +{ + char name[UB_TABLE_HEADER_NAME_LEN] = {}; + struct ub_table_header *header; + int ret = 0, i; + + if (ubios_table->count == 0) { + pr_err("ubios root table has no sub tables.\n"); + return 0; + } + pr_info("ubios sub table count is %u\n", ubios_table->count); + + for (i = 0; i < ubios_table->count; i++) { + header = (struct ub_table_header *)ub_table_get( + ubios_table->sub_tables[i]); + if (!header) + continue; + + memcpy(name, header->name, UB_TABLE_HEADER_NAME_LEN - 1); + pr_info("ubrt sub table name is %s\n", name); + ub_table_put(header); + + if (!strncmp(name, UBIOS_SIG_UBC, strlen(UBIOS_SIG_UBC))) + ret = handle_ubc_table(ubios_table->sub_tables[i]); + else if (!strncmp(name, UBIOS_SIG_UMMU, strlen(UBIOS_SIG_UMMU))) + ret = handle_ummu_table(ubios_table->sub_tables[i]); + else + pr_warn("Ignore sub table: %s\n", name); + + if (ret) { + pr_err("Create %s device ret=%d\n", name, ret); + goto out; + } + } + + return 0; + +out: + uninit_ub_nodes(); + return ret; +} diff --git a/drivers/ub/ubfi/ubrt.h b/drivers/ub/ubfi/ubrt.h new file mode 100644 index 0000000000000000000000000000000000000000..0cbc5fe823681a9c291f845002ceb8ceff511bab --- /dev/null +++ b/drivers/ub/ubfi/ubrt.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UBRT_H__ +#define __UBRT_H__ + +#include +#include + +#define UB_TABLE_HEADER_NAME_LEN 16 + +struct ub_table_header { + char name[UB_TABLE_HEADER_NAME_LEN]; + u32 total_size; + u8 version; + u8 reserved[3]; + u32 remaining_size; + u32 checksum; +}; + +struct ubios_root_table { + struct ub_table_header header; + u16 count; + u8 reserved[6]; + u64 sub_tables[]; +}; + +struct ubrt_sub_table { + u8 type; + u8 reserved[7]; + u64 pointer; +}; + +struct acpi_table_ubrt { + struct acpi_table_header head; + u32 count; + struct ubrt_sub_table sub_table[]; +}; + +enum ubrt_sub_table_type { + UB_BUS_CONTROLLER_TABLE = 0, + UMMU_TABLE = 1, + UB_RESERVED_MEMORY_TABLE = 2, + VIRTUAL_BUS_TABLE = 3, + CALL_ID_SERVICE_TABLE = 4, + UB_ENTITY_TABLE = 5, + UB_TOPOLOGY_TABLE = 6, +}; + +extern struct acpi_table_ubrt *acpi_table; +extern struct ubios_root_table *ubios_table; + +void *ub_table_get(u64 pa); +void ub_table_put(void *va); + +int handle_acpi_ubrt(void); +int handle_dts_ubrt(void); + +void uninit_ub_nodes(void); + +#endif /* __UBRT_H__ */ diff --git a/drivers/ub/ubfi/ummu.c b/drivers/ub/ubfi/ummu.c new file mode 100644 index 0000000000000000000000000000000000000000..93f6dcbf8aa605d837489589cd32d32c5875f28d --- /dev/null +++ b/drivers/ub/ubfi/ummu.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + * Descriptor: + * Parse and create ummu from UBRT(reported by ACPI) or UBIOS Info table (reported by DTS) + */ + +#define pr_fmt(fmt) "ubfi ummu: " fmt + +#include +#include +#include + +#include "ubrt.h" +#include "ub_fi.h" +#include "ubc.h" +#include "ummu.h" + +struct ummu_sub_table { + struct ub_table_header header; + u16 count; + u8 reserved[6]; + u8 node_data[]; __counted_by(count) +}; + +#define UBRT_UMMU_PXM_VALID 0xFFFF +#define ACPI_UMMU_DEVICE_HID "HISI0551" +#define ACPI_UMMU_PMU_DEVICE_HID "HISI0571" +#define UMMU_INDEX_MASK GENMASK(31, 0) +#define UMMU_TYPE_MASK GENMASK_ULL(63, 32) + +static int __init ummu_set_proximity(struct device *dev, struct ummu_node *node) +{ + int dev_node; + + if (node->pxm == UBRT_UMMU_PXM_VALID) + return 0; + + if (acpi_disabled) { + dev_node = node->pxm; + if (dev_node >= MAX_NUMNODES || !node_possible(dev_node)) + return -EINVAL; + } else { + dev_node = pxm_to_node(node->pxm); + if (dev_node != NUMA_NO_NODE && !node_online(dev_node)) + return -EINVAL; + } + + set_dev_node(dev, dev_node); + + dev_info(dev, "UMMU mapped to Proximity domain : %u dev_node : %d\n", + node->pxm, dev_node); + + return 0; +} + +static int __init ummu_count_resources(struct ummu_node *node) +{ + /* present mem resource only */ + return 1; +} + +static void __init ummu_pmu_dev_init_resources(struct resource *res, int cnt, + struct ummu_node *node) +{ + int num = 0; + + res[num].start = node->pmu_addr; + res[num].end = node->pmu_addr + node->pmu_size - 1; + res[num].flags = IORESOURCE_MEM; + num++; + + if (num != cnt) + pr_err("ummu pmu res num is not match!\n"); +} + +static void __init ummu_device_init_resources(struct resource *res, int cnt, + struct ummu_node *node) +{ + int num = 0; + + res[num].start = node->base_addr; + res[num].end = node->base_addr + node->addr_size - 1; + res[num].flags = IORESOURCE_MEM; + num++; + if (num != cnt) + pr_err("ummu res num is not match!\n"); +} + +static int __init ummu_add_resources(struct platform_device *pdev, + struct ummu_node *node, + enum ubrt_node_type type) +{ + struct resource *res __free(kfree); + int num_res; + + num_res = ummu_count_resources(node); + res = kcalloc(num_res, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + if (type == UBRT_UMMU) + ummu_device_init_resources(res, num_res, node); + else + ummu_pmu_dev_init_resources(res, num_res, node); + + return platform_device_add_resources(pdev, res, num_res); +} + +static int ummu_rename_device(struct platform_device *pdev, enum ubrt_node_type type) +{ + static int device_count; + char new_name[32]; + int ret; + + if (type == UBRT_UMMU) + ret = snprintf(new_name, sizeof(new_name), "ummu.%d", device_count); + else + ret = snprintf(new_name, sizeof(new_name), "ummu_pmu.%d", device_count); + + if (ret < 0 || ret >= sizeof(new_name)) { + dev_err(&pdev->dev, "failed to generate new device name\n"); + return -ENOENT; + } + + ret = device_rename(&pdev->dev, new_name); + if (ret) { + dev_err(&pdev->dev, "failed to rename device to %s: %d\n", new_name, ret); + return ret; + } + pdev->name = pdev->dev.kobj.name; + + device_count++; + + return 0; +} + +static int ummu_config_update(struct platform_device *pdev, + struct ummu_node *ummu_node, + enum ubrt_node_type type) +{ + int ret; + + if (!pdev->dev.msi.domain) + dev_warn(&pdev->dev, "can't find device msi domain.\n"); + + ret = ummu_rename_device(pdev, type); + if (ret) + return ret; + + ret = ummu_set_proximity(&pdev->dev, ummu_node); + if (ret) { + dev_err(&pdev->dev, "ummu set proximity failed ret[%d]\n", ret); + return ret; + } + + ret = ummu_add_resources(pdev, ummu_node, type); + if (ret) { + dev_err(&pdev->dev, "ummu add resources failed ret[%d]\n", ret); + return ret; + } + + if (type == UBRT_UMMU) { + ret = platform_device_add_data(pdev, ummu_node->vendor_info, + sizeof(ummu_node->vendor_info)); + if (ret) + return ret; + } + + dev_info(&pdev->dev, "interrupt_id[0x%x], pxm[%u], its_index[%u], vendor_id[0x%x]\n", + ((type == UBRT_UMMU) ? ummu_node->intr_id : ummu_node->pmu_intr_id), + ummu_node->pxm, ummu_node->its_index, ummu_node->vendor_id); + return 0; +} + +static acpi_status acpi_processor_ummu(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + struct platform_device *pdev; + struct acpi_device *adev; + enum ubrt_node_type type; + struct ummu_node *node; + struct ubrt_fwnode *fw; + unsigned long long uid; + acpi_status status; + struct device *dev; + u64 *node_flag; + u32 index; + int ret; + + node_flag = context; + index = FIELD_GET(UMMU_INDEX_MASK, *node_flag); + type = FIELD_GET(UMMU_TYPE_MASK, *node_flag); + fw = ubrt_fwnode_get_by_idx(index, type); + if (!fw) { + pr_err("can not get ubrt fwnode!\n"); + return AE_CTRL_FALSE; + } + + status = acpi_evaluate_integer(handle, "_UID", NULL, &uid); + if (ACPI_FAILURE(status)) { + pr_err("can not get dsdt uid. status[%u]\n", status); + return AE_CTRL_TERMINATE; + } + + if (index != (u32)uid) + return AE_OK; + + adev = acpi_get_acpi_dev(handle); + if (!adev) { + pr_err("acpi get device failed\n"); + return AE_CTRL_TERMINATE; + } + + dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev); + if (!dev) { + pr_err("platform device not found\n"); + status = AE_CTRL_TERMINATE; + goto out; + } + pdev = to_platform_device(dev); + node = (struct ummu_node *)fw->ubrt_node; + + ret = ummu_config_update(pdev, node, type); + if (ret) { + dev_err(dev, "update config failed, ret[%d]\n", ret); + status = AE_CTRL_FALSE; + goto out; + } + + ret = ubrt_fwnode_set(index, type, dev->fwnode); + if (ret) { + dev_err(dev, "update fwnode failed, ret[%d]\n", ret); + status = AE_CTRL_FALSE; + } + +out: + acpi_put_acpi_dev(adev); + return status; +} + +#ifdef CONFIG_ACPI +static int acpi_update_ummu_config(struct ummu_node *ummu_node, u32 index) +{ + acpi_status status; + u64 node_flag; + int ret; + + ret = ubrt_fwnode_add(ummu_node, index, sizeof(*ummu_node), UBRT_UMMU); + if (ret) { + pr_err("failed to add ummu fwnode! ret[%d]\n", ret); + return ret; + } + + node_flag = index | (((u64)UBRT_UMMU) << 32); + + status = acpi_get_devices(ACPI_UMMU_DEVICE_HID, + acpi_processor_ummu, + &node_flag, NULL); + if (ACPI_FAILURE(status)) { + pr_err("acpi get devices err, status[%u]\n", status); + goto ummu_err; + } + + ret = ubrt_fwnode_add(ummu_node, index, sizeof(struct ummu_node), UBRT_UMMU_PMU); + if (ret) { + pr_err("failed to add pmu fwnode! ret[%d]\n", ret); + goto ummu_err; + } + + node_flag = index | (((u64)UBRT_UMMU_PMU) << 32); + + /* Get UB PMU from DSDT */ + status = acpi_get_devices(ACPI_UMMU_PMU_DEVICE_HID, + acpi_processor_ummu, + &node_flag, NULL); + if (ACPI_FAILURE(status)) { + pr_err("acpi get devices err, status[%u]\n", status); + goto pmu_err; + } + + return 0; + +pmu_err: + ubrt_fwnode_del(index, UBRT_UMMU_PMU); +ummu_err: + ubrt_fwnode_del(index, UBRT_UMMU); + return -ENODEV; +} +#else +static inline int acpi_update_ummu_config(struct ummu_node *ummu_node, u32 index) +{ + return -ENODEV; +} +#endif /* CONFIG_ACPI */ + +#ifdef CONFIG_OF +static struct platform_device *ummu_of_find_plat_dev(struct device_node *node, u32 index) +{ + struct platform_device *pdev; + const char *node_name; + u32 dts_index; + int ret; + + node_name = of_node_full_name(node); + + ret = of_property_read_u32(node, "index", &dts_index); + if (ret) { + pr_err("dts can't find ummu ctl-no\n"); + return NULL; + } + + if (dts_index != index) { + pr_debug("ummu dts_index %u != index %u\n", dts_index, index); + return NULL; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_err("failed to find platform device for node: %s\n", node_name); + return NULL; + } + + return pdev; +} + +static int ummu_of_update_config(struct platform_device *pdev, + struct ummu_node *ummu_node, + u32 index, + enum ubrt_node_type type) +{ + int ret; + + ret = ubrt_fwnode_add(ummu_node, index, sizeof(struct ummu_node), type); + if (ret) { + dev_err(&pdev->dev, "failed to add ummu fwnode! ret[%d]\n", ret); + return ret; + } + + ret = ubrt_fwnode_set(index, type, pdev->dev.fwnode); + if (ret) { + dev_err(&pdev->dev, "update fwnode failed, ret[%d]\n", ret); + goto err; + } + + ret = ummu_config_update(pdev, ummu_node, type); + if (ret) { + dev_err(&pdev->dev, "update config failed, ret[%d]\n", ret); + goto err; + } + + return 0; +err: + ubrt_fwnode_del(index, type); + return ret; +} + +static int dts_update_ummu_config(struct ummu_node *ummu_node, u32 index) +{ + struct platform_device *pdev; + struct device_node *node; + int ret; + + for_each_compatible_node(node, NULL, "ub,ummu") { + pdev = ummu_of_find_plat_dev(node, index); + if (!pdev) + continue; + + ret = ummu_of_update_config(pdev, ummu_node, index, UBRT_UMMU); + if (ret) + return ret; + } + + for_each_compatible_node(node, NULL, "ub,ummu_pmu") { + pdev = ummu_of_find_plat_dev(node, index); + if (!pdev) + continue; + + ret = ummu_of_update_config(pdev, ummu_node, index, UBRT_UMMU_PMU); + if (ret) + return ret; + } + + return 0; +} +#else +static inline int dts_update_ummu_config(struct ummu_node *ummu_node, u32 index) +{ + return -ENODEV; +} +#endif /* CONFIG_OF */ + +static int parse_ummu(void *info_node) +{ + struct ummu_sub_table *sub_table; + struct ummu_node *ummu_node; + int ret; + u32 index; + + sub_table = (struct ummu_sub_table *)info_node; + if (!sub_table->count) { + pr_warn("info table has no ummu.\n"); + return 0; + } + + pr_info("ummu node num: %u\n", sub_table->count); + + ummu_node = (struct ummu_node *)sub_table->node_data; + for (index = 0; index < sub_table->count; index++, ummu_node++) { + if (acpi_disabled) + ret = dts_update_ummu_config(ummu_node, index); + else + ret = acpi_update_ummu_config(ummu_node, index); + + if (ret) { + pr_err("Create No.%u ummu failed, ret=%d\n", index, ret); + return ret; + } + } + + return 0; +} + +int handle_ummu_table(u64 pointer) +{ + void *info_node = ub_table_get(pointer); + int ret; + + if (!info_node) + return -EINVAL; + + ret = parse_ummu(info_node); + ub_table_put(info_node); + return ret; +} diff --git a/drivers/ub/ubfi/ummu.h b/drivers/ub/ubfi/ummu.h new file mode 100644 index 0000000000000000000000000000000000000000..e9c07c000a51b37cf7bf00dd7c7ca864b1e5007e --- /dev/null +++ b/drivers/ub/ubfi/ummu.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + * Descriptor: Parse and create ummu from UBRT + */ + +#ifndef __UBFI_UMMU_H__ +#define __UBFI_UMMU_H__ + +int handle_ummu_table(u64 pointer); + +#endif /* __UBFI_UMMU_H__ */ diff --git a/drivers/ub/ubus/Kconfig b/drivers/ub/ubus/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..a754f554628a591d662666f6ebf90d775448bb09 --- /dev/null +++ b/drivers/ub/ubus/Kconfig @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# UB bus driver configuration +# + +menuconfig UB_UBUS + default n + bool "UBUS support" + depends on UB + help + This option enables support for UB bus device management + functionality, providing fundamental capabilities such as + UB bus registration, device registration, and driver registration. + It configures and enables devices, offering device capabilities + to the upper layers. + Say 'Y' here unless you know what you are doing. + +if UB_UBUS + +config UB_UBUS_BUS + default n + tristate "UB Bus Driver Modular Section" + depends on UB_UBUS + help + This option enables the modular part of the UB bus driver, + which works in conjunction with the built-in part to implement + the UB bus device management functionality. + Say 'M' here unless you know what you are doing. + +config UB_UBUS_USI + default n + bool "UBUS Message Signaled Interrupts (USI)" + depends on UB_UBUS + select GENERIC_MSI_IRQ + help + This allows device drivers to enable MSI (Message Signaled + Interrupts). Message Signaled Interrupts enable a device to + generate an interrupt using an inbound Memory Write on its + UnifiedBus instead of asserting a device IRQ pin.If you + don't know what to do here, say Y. + +source "drivers/ub/ubus/vendor/Kconfig" + +endif diff --git a/drivers/ub/ubus/Makefile b/drivers/ub/ubus/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..569718ac74de91b0c4985eb0fb9650c725a483b4 --- /dev/null +++ b/drivers/ub/ubus/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_UB_UBUS) += ub-driver.o controller.o config.o entity.o ras.o +obj-$(CONFIG_UB_UBUS) += msi/ + +ubus-y := ubus_driver.o sysfs.o ubus_controller.o msg.o ubus_config.o port.o cc.o eid.o cna.o route.o +ubus-y += enum.o resource.o ubus_entity.o reset.o cap.o interrupt.o decoder.o ioctl.o eu.o link.o +ubus-y += instance.o pool.o memory.o ub_black_box.o + +ubus-y += services/ras.o services/service.o services/gucd.o +ubus-y += services/hotplug/hotplug_core.o services/hotplug/hotplug_ctrl.o +ubus-y += services/hotplug/hotplug_route.o services/hotplug/hotplug_msg.o + +obj-$(CONFIG_UB_UBUS_BUS) += ubus.o +obj-y += vendor/ diff --git a/drivers/ub/ubus/cap.c b/drivers/ub/ubus/cap.c new file mode 100644 index 0000000000000000000000000000000000000000..92d2069b4d5c38fea148a63ba4958340306757bb --- /dev/null +++ b/drivers/ub/ubus/cap.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus cap: " fmt + +#include "ubus.h" +#include "decoder.h" +#include "interrupt.h" +#include "cap.h" + +#define DW_CHECK 3 + +void ub_set_cap_bitmap(struct ub_entity *uent) +{ + int ret; + u32 i; + + for (i = 0; i < SZ_8; i++) { + ret = ub_cfg_read_dword(uent, UB_CFG1_CAP_BITMAP + (i << SZ_2), + &uent->cfg1_bitmap[i]); + if (ret) + ub_err(uent, "Read cfg1 cap bitmap failed, ret=%d\n", + ret); + } + + if (is_p_device(uent)) + return; + + for (i = 0; i < SZ_8; i++) { + ret = ub_cfg_read_dword(uent, UB_CFG0_CAP_BITMAP + (i << SZ_2), + &uent->cfg0_bitmap[i]); + if (ret) + ub_err(uent, "Read cfg0 cap bitmap failed, ret=%d\n", + ret); + } +} + +/* Check whether the capbility register is implemented. */ +static bool ub_cap_reg_implemented(struct ub_entity *uent, u32 cap) +{ + u32 i = (cap & 0xFF) / SZ_32; + u32 val = (cap >> SZ_8) ? uent->cfg1_bitmap[i] : uent->cfg0_bitmap[i]; + + return val & BIT((cap & 0xFF) % SZ_32); +} + +/* find the start address of capability */ +u32 ub_find_capability(u32 cap) +{ + return (cap << BITS_PER_BYTE) << SZ_2; +} + +int ub_cap_read_byte(struct ub_entity *uent, u32 cap, u32 off, u8 *val) +{ + int ret; + + *val = 0; + if (off >= SZ_1K) + return -EFAULT; + + if (!ub_cap_reg_implemented(uent, cap)) + return -ENXIO; + + ret = ub_cfg_read_byte(uent, ub_find_capability(cap) + off, val); + if (ret) + *val = 0; + + return ret; +} + +int ub_cap_read_word(struct ub_entity *uent, u32 cap, u32 off, u16 *val) +{ + int ret; + + *val = 0; + if (off & 1 || off >= SZ_1K) + return -EFAULT; + + if (!ub_cap_reg_implemented(uent, cap)) + return -ENXIO; + + ret = ub_cfg_read_word(uent, ub_find_capability(cap) + off, val); + if (ret) + *val = 0; + + return ret; +} + +int ub_cap_read_dword(struct ub_entity *uent, u32 cap, u32 off, u32 *val) +{ + int ret; + + *val = 0; + if (off & DW_CHECK || off >= SZ_1K) + return -EFAULT; + + if (!ub_cap_reg_implemented(uent, cap)) + return -ENXIO; + + ret = ub_cfg_read_dword(uent, ub_find_capability(cap) + off, val); + if (ret) + *val = 0; + + return ret; +} + +int ub_cap_write_byte(struct ub_entity *uent, u32 cap, u32 off, u8 val) +{ + if (off >= SZ_1K) + return -EFAULT; + + if (!ub_cap_reg_implemented(uent, cap)) + return -ENXIO; + + return ub_cfg_write_byte(uent, ub_find_capability(cap) + off, val); +} + +int ub_cap_write_word(struct ub_entity *uent, u32 cap, u32 off, u16 val) +{ + if (off & 1 || off >= SZ_1K) + return -EFAULT; + + if (!ub_cap_reg_implemented(uent, cap)) + return -ENXIO; + + return ub_cfg_write_word(uent, ub_find_capability(cap) + off, val); +} + +int ub_cap_write_dword(struct ub_entity *uent, u32 cap, u32 off, u32 val) +{ + if (off & DW_CHECK || off >= SZ_1K) + return -EFAULT; + + if (!ub_cap_reg_implemented(uent, cap)) + return -ENXIO; + + return ub_cfg_write_dword(uent, ub_find_capability(cap) + off, val); +} + +int ub_cap_clear_and_set_word(struct ub_entity *dev, u32 cap, u32 off, + u16 clear, u16 set) +{ + u16 val; + int ret; + + ret = ub_cap_read_word(dev, cap, off, &val); + if (!ret) { + val &= ~clear; + val |= set; + ret = ub_cap_write_word(dev, cap, off, val); + } + + return ret; +} + +int ub_cap_clear_and_set_dword(struct ub_entity *dev, u32 cap, u32 off, + u32 clear, u32 set) +{ + u32 val; + int ret; + + ret = ub_cap_read_dword(dev, cap, off, &val); + if (!ret) { + val &= ~clear; + val |= set; + ret = ub_cap_write_dword(dev, cap, off, val); + } + + return ret; +} + +static inline int ub_cap_set_word(struct ub_entity *dev, u32 cap, u32 off, + u16 set) +{ + return ub_cap_clear_and_set_word(dev, cap, off, 0, set); +} + +static inline int ub_cap_set_dword(struct ub_entity *dev, u32 cap, u32 off, + u32 set) +{ + return ub_cap_clear_and_set_dword(dev, cap, off, 0, set); +} + +static inline int ub_cap_clear_word(struct ub_entity *dev, u32 cap, u32 off, + u16 clear) +{ + return ub_cap_clear_and_set_word(dev, cap, off, clear, 0); +} + +static inline int ub_cap_clear_dword(struct ub_entity *dev, u32 cap, u32 off, + u32 clear) +{ + return ub_cap_clear_and_set_dword(dev, cap, off, clear, 0); +} + +static int ub_sf_init(struct ub_entity *uent) +{ + u32 support_feature = 0; + int ret; + + ret = ub_cfg_read_dword(uent, UB_FEATURE_SUPPORT_0, &support_feature); + if (ret) { + ub_err(uent, "Read support feature0 failed, ret=%d\n", ret); + return ret; + } + + uent->support_feature = support_feature; + return 0; +} + +static void ub_sw_init(struct ub_entity *uent) +{ + if (!uent || !uent->ubc) + return; + + if (!is_ibus_controller(uent)) + return; + + if (uent->support_feature & UB_SW_SUPPORT) + uent->sw_cap = true; + else + uent->sw_cap = false; +} + +void ub_init_capabilities(struct ub_entity *uent) +{ + /* cfg0 caps */ + if (ub_sf_init(uent)) + goto init_cfg1_cap; + + ub_sw_init(uent); + +init_cfg1_cap: + /* cfg1 caps */ + ub_decoder_init(uent); + ub_intr_init(uent); + + uent->reset_fn = 1; +} + +void ub_uninit_capabilities(struct ub_entity *uent) +{ + /* cfg1 cap */ + ub_decoder_uninit(uent); + + uent->reset_fn = 0; +} diff --git a/drivers/ub/ubus/cap.h b/drivers/ub/ubus/cap.h new file mode 100644 index 0000000000000000000000000000000000000000..11507c64601719b501a160df2ac69d060dd2dd03 --- /dev/null +++ b/drivers/ub/ubus/cap.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __CAP_H__ +#define __CAP_H__ + +void ub_set_cap_bitmap(struct ub_entity *uent); +u32 ub_find_capability(u32 cap); +void ub_init_capabilities(struct ub_entity *uent); +void ub_uninit_capabilities(struct ub_entity *uent); + +#endif /* __CAP_H__ */ diff --git a/drivers/ub/ubus/cc.c b/drivers/ub/ubus/cc.c new file mode 100644 index 0000000000000000000000000000000000000000..5f20d047c2e4e46ddfc4270b5d843a3ed6b3ae28 --- /dev/null +++ b/drivers/ub/ubus/cc.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus cc: " fmt + +#include "ubus.h" + +static int ub_cc_safe_check(struct ub_entity *uent) +{ + if (!uent) + return -EINVAL; + + if (is_idev(uent)) { + if (!uent->ubc || !uent->ubc->uent) + return -EINVAL; + } + + return 0; +} + +bool ub_cc_supported(struct ub_entity *uent) +{ + if (ub_cc_safe_check(uent)) + return false; + + if (is_idev(uent)) { + /* Now, idev cc enable depends ub bus controller cc enable */ + if ((uent->ubc->uent->support_feature & UB_CC_SUPPORT) && + (uent->support_feature & UB_CC_SUPPORT)) + return true; + } else { + if (uent->support_feature & UB_CC_SUPPORT) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(ub_cc_supported); + +/** + * ub_cc_set - Set congestion control for given ub entity + * @uent: ub entity + * @val: cc switch + * + * Returns: 0 on success, an error otherwise. + */ +static int ub_cc_set(struct ub_entity *uent, u8 val) +{ + if (!ub_cc_supported(uent)) + return -EPERM; + + return ub_cfg_write_byte(uent, UB_CC_EN, val); +} + +/** + * ub_cc_enable - Enable congestion control for given ub entity + * @uent: ub entity + * + * Returns: 0 on success, an error otherwise. + */ +int ub_cc_enable(struct ub_entity *uent) +{ + if (ub_cc_safe_check(uent)) + return -EINVAL; + + return ub_cc_set(uent, 1); +} +EXPORT_SYMBOL_GPL(ub_cc_enable); + +/** + * ub_cc_disable - Disable congestion control for given ub entity + * @uent: ub entity + * + * Returns: 0 on success, an error otherwise. + */ +int ub_cc_disable(struct ub_entity *uent) +{ + if (ub_cc_safe_check(uent)) + return -EINVAL; + + return ub_cc_set(uent, 0); +} +EXPORT_SYMBOL_GPL(ub_cc_disable); diff --git a/drivers/ub/ubus/cna.c b/drivers/ub/ubus/cna.c new file mode 100644 index 0000000000000000000000000000000000000000..e435259b141163b3104c847cd3ce5408f63890fc --- /dev/null +++ b/drivers/ub/ubus/cna.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus cna: " fmt + +#include "ubus.h" +#include "port.h" +#include "cna.h" + +static DEFINE_IDA(ub_cna_ida); + +static int ub_alloc_cna_id(void) +{ + return ida_alloc_range(&ub_cna_ida, ubc_cna_start, ubc_cna_end, GFP_KERNEL); +} + +static void ub_free_cna_id(u32 cna) +{ + ida_free(&ub_cna_ida, cna); +} + +void ub_cna_free(struct ub_entity *uent) +{ + struct ub_port *port; + + if (!is_primary(uent)) { + ub_warn(uent, "only entity0 need to free cna\n"); + return; + } + + for_each_uent_port(port, uent) { + if (port->domain_boundary && is_ibus_controller(uent)) + continue; + + if (port->cna) { + if (!ONE_CNA(uent)) + ub_free_cna_id(port->cna); + port->cna = 0; + } + } + + if (is_ibus_controller(uent) && uent->ubc->cluster) + return; + + if (uent->cna) { + ub_free_cna_id(uent->cna); + uent->cna = 0; + } +} + +int ub_cna_alloc(struct ub_entity *uent) +{ + bool one_cna = ONE_CNA(uent); + struct ub_port *port; + int ret; + + if (!is_primary(uent)) { + ub_warn(uent, "only entity0 need to alloc cna\n"); + return 0; + } + + if (!(is_ibus_controller(uent) && uent->ubc->cluster)) { + ret = ub_alloc_cna_id(); + if (ret < 0) + return ret; + + uent->cna = (u32)ret; + } + + for_each_uent_port(port, uent) { + if (is_ibus_controller(uent) && port->domain_boundary) + continue; + if (one_cna) { + port->cna = uent->cna; + } else { + ret = ub_alloc_cna_id(); + if (ret < 0) + return ret; + port->cna = (u32)ret; + } + } + + return 0; +} diff --git a/drivers/ub/ubus/cna.h b/drivers/ub/ubus/cna.h new file mode 100644 index 0000000000000000000000000000000000000000..68d3d9a97fcc6c49e492e1eb4f9bd44609788467 --- /dev/null +++ b/drivers/ub/ubus/cna.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __CNA_H__ +#define __CNA_H__ + +struct ub_entity; +int ub_cna_alloc(struct ub_entity *uent); +void ub_cna_free(struct ub_entity *uent); + +#endif /* __CNA_H__ */ diff --git a/drivers/ub/ubus/config.c b/drivers/ub/ubus/config.c new file mode 100644 index 0000000000000000000000000000000000000000..721a9c62027b60ef0cb7bf52b0bade524efa4a7f --- /dev/null +++ b/drivers/ub/ubus/config.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "ubus_inner.h" + +static const struct cfg_ops { + read_byte_f cfg_read_byte; + read_word_f cfg_read_word; + read_dword_f cfg_read_dword; + write_byte_f cfg_write_byte; + write_word_f cfg_write_word; + write_dword_f cfg_write_dword; +} cfg_ops_t; + +static struct cfg_ops ub_cfg_ops = {}; + +int register_ub_cfg_read_ops(read_byte_f rb, read_word_f rw, read_dword_f rdw) +{ + if (rb && rw && rdw) { + ub_cfg_ops.cfg_read_byte = rb; + ub_cfg_ops.cfg_read_word = rw; + ub_cfg_ops.cfg_read_dword = rdw; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(register_ub_cfg_read_ops); + +int register_ub_cfg_write_ops(write_byte_f wb, write_word_f ww, write_dword_f wdw) +{ + if (wb && ww && wdw) { + ub_cfg_ops.cfg_write_byte = wb; + ub_cfg_ops.cfg_write_word = ww; + ub_cfg_ops.cfg_write_dword = wdw; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(register_ub_cfg_write_ops); + +void unregister_ub_cfg_ops(void) +{ + memset(&ub_cfg_ops, 0, sizeof(struct cfg_ops)); +} +EXPORT_SYMBOL_GPL(unregister_ub_cfg_ops); + +int ub_cfg_read_byte(struct ub_entity *uent, u64 pos, u8 *val) +{ + if (!ub_cfg_ops.cfg_read_byte) + return -ENODEV; + + return ub_cfg_ops.cfg_read_byte(uent, pos, val); +} +EXPORT_SYMBOL_GPL(ub_cfg_read_byte); + +int ub_cfg_read_word(struct ub_entity *uent, u64 pos, u16 *val) +{ + if (!ub_cfg_ops.cfg_read_word) + return -ENODEV; + + return ub_cfg_ops.cfg_read_word(uent, pos, val); +} +EXPORT_SYMBOL_GPL(ub_cfg_read_word); + +int ub_cfg_read_dword(struct ub_entity *uent, u64 pos, u32 *val) +{ + if (!ub_cfg_ops.cfg_read_dword) + return -ENODEV; + + return ub_cfg_ops.cfg_read_dword(uent, pos, val); +} +EXPORT_SYMBOL_GPL(ub_cfg_read_dword); + +int ub_cfg_write_byte(struct ub_entity *uent, u64 pos, u8 val) +{ + if (!ub_cfg_ops.cfg_write_byte) + return -ENODEV; + + return ub_cfg_ops.cfg_write_byte(uent, pos, val); +} +EXPORT_SYMBOL_GPL(ub_cfg_write_byte); + +int ub_cfg_write_word(struct ub_entity *uent, u64 pos, u16 val) +{ + if (!ub_cfg_ops.cfg_write_word) + return -ENODEV; + + return ub_cfg_ops.cfg_write_word(uent, pos, val); +} +EXPORT_SYMBOL_GPL(ub_cfg_write_word); + +int ub_cfg_write_dword(struct ub_entity *uent, u64 pos, u32 val) +{ + if (!ub_cfg_ops.cfg_write_dword) + return -ENODEV; + + return ub_cfg_ops.cfg_write_dword(uent, pos, val); +} +EXPORT_SYMBOL_GPL(ub_cfg_write_dword); diff --git a/drivers/ub/ubus/controller.c b/drivers/ub/ubus/controller.c new file mode 100644 index 0000000000000000000000000000000000000000..72a7b9a0d12d288b9d800b0cd470c6f32f3f9893 --- /dev/null +++ b/drivers/ub/ubus/controller.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "ubus_inner.h" + +struct ub_bus_controller *ub_ubc_get(struct ub_bus_controller *ubc) +{ + if (ubc) + get_device(&ubc->dev); + return ubc; +} +EXPORT_SYMBOL_GPL(ub_ubc_get); + +void ub_ubc_put(struct ub_bus_controller *ubc) +{ + if (ubc) + put_device(&ubc->dev); +} +EXPORT_SYMBOL_GPL(ub_ubc_put); diff --git a/drivers/ub/ubus/decoder.c b/drivers/ub/ubus/decoder.c new file mode 100644 index 0000000000000000000000000000000000000000..93dca30e9c50c82ad1acaf588d2ecfe127a7f610 --- /dev/null +++ b/drivers/ub/ubus/decoder.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus decoder: " fmt + +#include +#include +#include +#include + +#include "ubus.h" +#include "ubus_controller.h" +#include "decoder.h" + +#define MMIO_SIZE_MASK GENMASK_ULL(18, 16) +#define CMDQ_SIZE_MASK GENMASK_ULL(15, 12) +#define EVTQ_SIZE_MASK GENMASK_ULL(7, 4) +#define MMIO_SIZE_OFFSET 16 +#define CMDQ_SIZE_OFFSET 12 +#define EVTQ_SIZE_OFFSET 4 + +#define CMDQ_SIZE_USE_MASK GENMASK(11, 8) +#define CMDQ_SIZE_USE_OFFSET 8 +#define CMDQ_ENABLE 0x1 + +#define EVTQ_SIZE_USE_MASK GENMASK(11, 8) +#define EVTQ_SIZE_USE_OFFSET 8 +#define EVTQ_ENABLE 0x1 + +static void ub_decoder_uninit_queue(struct ub_bus_controller *ubc, + struct ub_decoder *decoder) +{ + if (ubc->ops->uninit_decoder_queue) + ubc->ops->uninit_decoder_queue(decoder); + else + ub_err(ubc->uent, "ub bus controller can't uninit decoder queue\n"); +} + +static int ub_decoder_init_queue(struct ub_bus_controller *ubc, + struct ub_decoder *decoder) +{ + if (ubc->ops->init_decoder_queue && ubc->ops->uninit_decoder_queue) + return ubc->ops->init_decoder_queue(decoder); + + ub_err(ubc->uent, "ub bus controller can't init decoder queue\n"); + return -EINVAL; +} + +static u32 set_mmio_base_reg(struct ub_decoder *decoder) +{ + u32 mmio_high = upper_32_bits(decoder->mmio_base_addr); + u32 mmio_low = lower_32_bits(decoder->mmio_base_addr); + struct ub_entity *ent = decoder->uent; + u32 low_bit, high_bit, ret; + + if (!ent->ubc->cluster) { + ret = (u32)ub_cfg_write_dword(ent, DECODER_MMIO_BA0, + 0xffffffff); + ret |= (u32)ub_cfg_write_dword(ent, DECODER_MMIO_BA1, + 0xffffffff); + ret |= (u32)ub_cfg_read_dword(ent, DECODER_MMIO_BA0, &low_bit); + ret |= (u32)ub_cfg_read_dword(ent, DECODER_MMIO_BA1, &high_bit); + if (ret) { + ub_err(ent, "Failed to access decoder MMIO BA\n"); + return ret; + } + + if ((low_bit | mmio_low) != low_bit || + (high_bit | mmio_high) != high_bit) { + ub_err(ent, "decoder MMIO address does not match HW reg\n"); + return -EINVAL; + } + } + + ret = (u32)ub_cfg_write_dword(decoder->uent, DECODER_MMIO_BA0, + lower_32_bits(decoder->mmio_base_addr)); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_MMIO_BA1, + upper_32_bits(decoder->mmio_base_addr)); + + return ret; +} + +static u32 set_page_table_reg(struct ub_decoder *decoder) +{ + u32 matt_high = upper_32_bits(decoder->pgtlb.pgtlb_dma); + u32 matt_low = lower_32_bits(decoder->pgtlb.pgtlb_dma); + struct ub_entity *ent = decoder->uent; + u32 low_bit, high_bit, ret; + + if (!ent->ubc->cluster) { + ret = (u32)ub_cfg_write_dword(ent, DECODER_MATT_BA0, + 0xffffffff); + ret |= (u32)ub_cfg_write_dword(ent, DECODER_MATT_BA1, + 0xffffffff); + ret |= (u32)ub_cfg_read_dword(ent, DECODER_MATT_BA0, &low_bit); + ret |= (u32)ub_cfg_read_dword(ent, DECODER_MATT_BA1, &high_bit); + if (ret) { + ub_err(ent, "Failed to access decoder MATT BA\n"); + return ret; + } + + if ((low_bit | matt_low) != low_bit || + (high_bit | matt_high) != high_bit) { + ub_err(ent, "decoder MATT address does not match HW reg\n"); + return -EINVAL; + } + } + + ret = (u32)ub_cfg_write_dword(decoder->uent, DECODER_MATT_BA0, + lower_32_bits(decoder->pgtlb.pgtlb_dma)); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_MATT_BA1, + upper_32_bits(decoder->pgtlb.pgtlb_dma)); + + return ret; +} + +static u32 set_queue_reg(struct ub_decoder *decoder) +{ + u32 val, ret; + + /* init decoder cmdq base addr and pi ci */ + ret = (u32)ub_cfg_write_dword(decoder->uent, DECODER_CMDQ_BASE_ADDR0, + lower_32_bits(decoder->cmdq.base)); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_CMDQ_BASE_ADDR1, + upper_32_bits(decoder->cmdq.base)); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_CMDQ_PROD, 0); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_CMDQ_CONS, 0); + + /* set decoder cmdq conf */ + ret |= (u32)ub_cfg_read_dword(decoder->uent, DECODER_CMDQ_CFG, &val); + decoder->vals.cmdq_cfg_val = val; + val &= ~CMDQ_SIZE_USE_MASK; + val |= decoder->cmdq.qs << CMDQ_SIZE_USE_OFFSET; + val |= CMDQ_ENABLE; + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_CMDQ_CFG, val); + + /* init decoder eventq base addr and pi ci */ + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_EVENTQ_BASE_ADDR0, + lower_32_bits(decoder->evtq.base)); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_EVENTQ_BASE_ADDR1, + upper_32_bits(decoder->evtq.base)); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_EVENTQ_PROD, 0); + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_EVENTQ_CONS, 0); + + /* set decoder eventq conf */ + ret |= (u32)ub_cfg_read_dword(decoder->uent, DECODER_EVENTQ_CFG, &val); + decoder->vals.evtq_cfg_val = val; + val &= ~EVTQ_SIZE_USE_MASK; + val |= decoder->evtq.qs << EVTQ_SIZE_USE_OFFSET; + val |= EVTQ_ENABLE; + ret |= (u32)ub_cfg_write_dword(decoder->uent, DECODER_EVENTQ_CFG, val); + + decoder->cmdq.prod.val = 0; + decoder->evtq.cons.val = 0; + if (ret) + ub_err(decoder->uent, "set decoder queue failed\n"); + + return ret; +} + +static void unset_queue_reg(struct ub_decoder *decoder) +{ + struct ub_entity *uent = decoder->uent; + u32 ret; + + ret = (u32)ub_cfg_write_dword(uent, DECODER_CMDQ_CFG, + decoder->vals.cmdq_cfg_val); + ret |= (u32)ub_cfg_write_dword(uent, DECODER_EVENTQ_CFG, + decoder->vals.evtq_cfg_val); + + ret |= (u32)ub_cfg_write_dword(uent, DECODER_CMDQ_BASE_ADDR0, 0); + ret |= (u32)ub_cfg_write_dword(uent, DECODER_CMDQ_BASE_ADDR1, 0); + + ret |= (u32)ub_cfg_write_dword(uent, DECODER_EVENTQ_BASE_ADDR0, 0); + ret |= (u32)ub_cfg_write_dword(uent, DECODER_EVENTQ_BASE_ADDR1, 0); + if (ret) + ub_err(uent, "unset queue reg fail\n"); +} + +static u32 set_decoder_enable(struct ub_decoder *decoder) +{ + u32 ret = (u32)ub_cfg_write_dword(decoder->uent, DECODER_CTRL, 1); + + if (ret) + ub_err(decoder->uent, "set decoder enable failed\n"); + + return ret; +} + +static void unset_decoder_enable(struct ub_decoder *decoder) +{ + struct ub_entity *uent = decoder->uent; + + if (ub_cfg_write_dword(uent, DECODER_CTRL, 0)) + ub_err(uent, "unset decoder enable fail\n"); +} + +static u32 ub_decoder_device_set(struct ub_decoder *decoder) +{ + u32 ret; + + ret = set_mmio_base_reg(decoder); + ret |= set_page_table_reg(decoder); + ret |= set_queue_reg(decoder); + ret |= set_decoder_enable(decoder); + + if (ret) { + unset_decoder_enable(decoder); + unset_queue_reg(decoder); + } + + return ret; +} + +static int ub_decoder_create_page_table(struct ub_bus_controller *ubc, + struct ub_decoder *decoder) +{ + if (ubc->ops->create_decoder_table && ubc->ops->free_decoder_table) + return ubc->ops->create_decoder_table(decoder); + + ub_err(decoder->uent, "ub bus controller can't create decoder table\n"); + return -EINVAL; +} + +static void ub_decoder_free_page_table(struct ub_bus_controller *ubc, + struct ub_decoder *decoder) +{ + if (ubc->ops->free_decoder_table) + ubc->ops->free_decoder_table(decoder); + else + ub_err(decoder->uent, + "ub bus controller can't free decoder table\n"); +} +static int ub_get_decoder_mmio_base(struct ub_bus_controller *ubc, + struct ub_decoder *decoder) +{ + struct resource_entry *entry; + + resource_list_for_each_entry(entry, &ubc->resources) { + if (entry->res->flags == IORESOURCE_MEM && + strstr(entry->res->name, "UB_BUS_CTL")) { + decoder->mmio_base_addr = entry->res->start; + decoder->mmio_end_addr = entry->res->end; + break; + } + } + + if (decoder->mmio_base_addr == 0) { + ub_err(decoder->uent, "get decoder mmio base failed\n"); + return -EINVAL; + } + + return 0; +} + +static const char * const mmio_size_desc[] = { + "128Gbyte", "256Gbyte", "512Gbyte", "1Tbyte", + "2Tbyte", "4Tbyte", "8Tbyte", "16Tbyte" +}; + +static const u64 mmio_size[] = { + 128ULL * SZ_1G, 256ULL * SZ_1G, 512ULL * SZ_1G, SZ_1T, + 2 * SZ_1T, 4 * SZ_1T, 8 * SZ_1T, 16 * SZ_1T +}; + +static int ub_get_decoder_cap(struct ub_decoder *decoder) +{ + struct ub_entity *uent = decoder->uent; + u64 size; + u32 val; + int ret; + + ret = ub_cfg_read_dword(uent, DECODER_CAP, &val); + if (ret) { + ub_err(uent, "read decoder cap fail\n"); + return ret; + } + + decoder->mmio_size_sup = (val & MMIO_SIZE_MASK) >> MMIO_SIZE_OFFSET; + decoder->cmdq.qs = (val & CMDQ_SIZE_MASK) >> CMDQ_SIZE_OFFSET; + decoder->evtq.qs = (val & EVTQ_SIZE_MASK) >> EVTQ_SIZE_OFFSET; + + if (decoder->cmdq.qs == 0 || decoder->evtq.qs == 0) { + ub_err(uent, "decoder cmdq or evtq qs is 0\n"); + return -EINVAL; + } + + size = decoder->mmio_end_addr - decoder->mmio_base_addr + 1; + if (size > mmio_size[decoder->mmio_size_sup]) + decoder->mmio_end_addr = decoder->mmio_base_addr + + mmio_size[decoder->mmio_size_sup] - 1; + + ub_info(uent, "decoder mmio_addr[%#llx-%#llx], cmdq_queue_size=%u, evtq_queue_size=%u, mmio_size_sup=%s\n", + decoder->mmio_base_addr, decoder->mmio_end_addr, + decoder->cmdq.qs, decoder->evtq.qs, + mmio_size_desc[decoder->mmio_size_sup]); + return 0; +} + +static int ub_create_decoder(struct ub_bus_controller *ubc) +{ + struct ub_entity *uent = ubc->uent; + struct ub_decoder *decoder; + int ret; + + decoder = kzalloc(sizeof(*decoder), GFP_KERNEL); + if (!decoder) + return -ENOMEM; + + decoder->dev = &ubc->dev; + decoder->uent = uent; + mutex_init(&decoder->table_lock); + + ret = ub_get_decoder_mmio_base(ubc, decoder); + if (ret) + goto release_decoder; + + ret = ub_get_decoder_cap(decoder); + if (ret) + goto release_decoder; + + ret = ub_decoder_init_queue(ubc, decoder); + if (ret) + goto release_decoder; + + ret = ub_decoder_create_page_table(ubc, decoder); + if (ret) { + ub_err(uent, "decoder create page table failed\n"); + goto release_queue; + } + + ret = ub_decoder_device_set(decoder); + if (ret) + goto release_page_table; + + ubc->decoder = decoder; + + decoder->irq_num = -1; + decoder->rg_size = SZ_4G; + + ub_info(uent, "decoder create success\n"); + return ret; + +release_page_table: + ub_decoder_free_page_table(ubc, decoder); +release_queue: + ub_decoder_uninit_queue(ubc, decoder); +release_decoder: + kfree(decoder); + return ret; +} + +static void unset_mmio_base_reg(struct ub_decoder *decoder) +{ + struct ub_entity *uent = decoder->uent; + u32 ret; + + ret = (u32)ub_cfg_write_dword(uent, DECODER_MMIO_BA0, 0); + ret |= (u32)ub_cfg_write_dword(uent, DECODER_MMIO_BA1, 0); + if (ret) + ub_err(uent, "unset mmio base reg failed\n"); +} + +static void unset_page_table_reg(struct ub_decoder *decoder) +{ + struct ub_entity *uent = decoder->uent; + u32 ret; + + ret = (u32)ub_cfg_write_dword(uent, DECODER_MATT_BA0, 0); + ret |= (u32)ub_cfg_write_dword(uent, DECODER_MATT_BA1, 0); + if (ret) + ub_err(uent, "unset page table reg failed\n"); +} + +static void ub_decoder_device_unset(struct ub_decoder *decoder) +{ + unset_decoder_enable(decoder); + unset_queue_reg(decoder); + unset_page_table_reg(decoder); + unset_mmio_base_reg(decoder); +} + +static void ub_remove_decoder(struct ub_bus_controller *ubc) +{ + struct ub_decoder *decoder = ubc->decoder; + + if (!decoder) { + ub_err(ubc->uent, "remove decoder, decoder is null\n"); + return; + } + + ub_decoder_device_unset(decoder); + + ub_decoder_free_page_table(ubc, decoder); + + ub_decoder_uninit_queue(ubc, decoder); + + kfree(decoder); + + ubc->decoder = NULL; +} + +static irqreturn_t decoder_event_deal_handle(int irq, void *data) +{ + struct ub_entity *uent = (struct ub_entity *)data; + struct ub_decoder *decoder = uent->ubc->decoder; + if (!decoder) { + ub_err(uent, "decoder does not exist\n"); + return IRQ_NONE; + } + + if (!uent->ubc->ops->decoder_event_deal) { + ub_err(uent, "decoder event deal does not exist\n"); + return IRQ_NONE; + } + + uent->ubc->ops->decoder_event_deal(decoder); + return IRQ_HANDLED; +} + +void ub_init_decoder_usi(struct ub_entity *uent) +{ + int irq_num, ret; + u32 usi_idx; + + if (!uent->ubc->decoder) { + ub_err(uent, "decoder not exist, can't init usi\n"); + return; + } + + ret = ub_cfg_read_dword(uent, DECODER_USI_IDX, &usi_idx); + if (ret) { + ub_err(uent, "get decoder usi idx failed\n"); + return; + } + + irq_num = ub_irq_vector(uent, usi_idx); + if (irq_num < 0) { + ub_err(uent, "ub get irq vector failed, ret=%d\n", irq_num); + return; + } + + ret = request_irq((unsigned int)irq_num, decoder_event_deal_handle, + IRQF_SHARED, "decoder_event_handle", (void *)uent); + if (ret) + ub_err(uent, "decoder request_irq failed, ret=%d\n", ret); + else + uent->ubc->decoder->irq_num = irq_num; +} + +void ub_uninit_decoder_usi(struct ub_entity *uent) +{ + int irq_num; + + if (!uent->ubc->decoder) { + ub_err(uent, "decoder not exist, can't uninit usi\n"); + return; + } + + irq_num = uent->ubc->decoder->irq_num; + if (irq_num < 0) + return; + + free_irq((unsigned int)irq_num, (void *)uent); +} + +void ub_decoder_init(struct ub_entity *uent) +{ + int ret; + + if (!uent || !uent->ubc) + return; + + if (!is_ibus_controller(uent)) + return; + + ret = ub_create_decoder(uent->ubc); + WARN_ON(ret); +} + +void ub_decoder_uninit(struct ub_entity *uent) +{ + if (!uent || !uent->ubc) + return; + + if (!is_ibus_controller(uent)) + return; + + ub_remove_decoder(uent->ubc); +} + +int ub_decoder_unmap(struct ub_decoder *decoder, phys_addr_t addr, u64 size) +{ + struct ub_bus_controller *ubc; + + if (!decoder) { + pr_err("unmap mmio decoder ptr is null\n"); + return -EINVAL; + } + + ubc = decoder->uent->ubc; + if (!ubc->ops->decoder_unmap) { + pr_err("decoder_unmap ops not exist\n"); + return -EINVAL; + } + return ubc->ops->decoder_unmap(ubc->decoder, addr, size); +} + +int ub_decoder_map(struct ub_decoder *decoder, struct decoder_map_info *info) +{ + struct ub_bus_controller *ubc; + + if (!decoder || !info) { + pr_err("decoder or map info is null\n"); + return -EINVAL; + } + + ubc = decoder->uent->ubc; + if (!ubc->ops->decoder_map || !ubc->ops->decoder_unmap) { + pr_err("decoder_map or decoder_unmap ops not exist\n"); + return -EINVAL; + } + + return ubc->ops->decoder_map(ubc->decoder, info); +} diff --git a/drivers/ub/ubus/decoder.h b/drivers/ub/ubus/decoder.h new file mode 100644 index 0000000000000000000000000000000000000000..48ffe9102a46335aafdc0315b1e2b7ec185b030d --- /dev/null +++ b/drivers/ub/ubus/decoder.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __DECODER_H__ +#define __DECODER_H__ + +#include + +enum ub_cmd_op_type { + INVALID = 0x0, + SYNC = 0x1, + TLBI_ALL = 0x2, + TLBI_PARTIAL = 0x3, +}; + +struct queue_idx { + union { + u32 val; + struct { + u32 cmdq_wr_idx : 11; + u32 reserved1 : 5; + u32 cmdq_err_resp : 1; + u32 reserved2 : 15; + }; + struct { + u32 cmdq_rd_idx : 11; + u32 reserved3 : 5; + u32 cmdq_err : 1; + u32 cmdq_err_reason : 3; + u32 reserved4 : 12; + }; + struct { + u32 eventq_wr_idx : 11; + u32 reserved5 : 21; + }; + struct { + u32 eventq_rd_idx : 11; + u32 reserved6 : 20; + u32 eventq_ovfl_err_resp : 1; + }; + }; +}; + +struct ub_decoder_queue { + phys_addr_t base; + void __iomem *qbase; + u32 qs; + struct queue_idx prod; + struct queue_idx cons; +}; + +struct reg_default_val { + u32 cmdq_cfg_val; + u32 evtq_cfg_val; +}; + +struct page_table_desc { + void *page_base; + dma_addr_t page_dma; + u32 count; +}; + +struct page_table { + void *pgtlb_base; + dma_addr_t pgtlb_dma; + struct page_table_desc *desc_base; +}; + +struct ub_decoder { + struct device *dev; + struct ub_entity *uent; + phys_addr_t mmio_base_addr; + phys_addr_t mmio_end_addr; + u32 mmio_size_sup; + u64 rg_size; + struct ub_decoder_queue cmdq; + struct ub_decoder_queue evtq; + struct page_table pgtlb; + struct reg_default_val vals; + void __iomem *notify; + int irq_num; + + struct page_table_desc invalid_desc; + u64 invalid_page_dma; + + struct mutex table_lock; +}; + +struct decoder_map_info { + phys_addr_t pa; + phys_addr_t uba; + u64 size; + u32 tpg_num; + u8 order_id; + u8 order_type; + u64 eid_low; + u64 eid_high; + u32 token_id; + u32 token_value; + u32 upi; + u32 src_eid; +}; + +void ub_decoder_init(struct ub_entity *uent); +void ub_decoder_uninit(struct ub_entity *uent); +void ub_init_decoder_usi(struct ub_entity *uent); +void ub_uninit_decoder_usi(struct ub_entity *uent); +int ub_decoder_map(struct ub_decoder *decoder, struct decoder_map_info *info); +int ub_decoder_unmap(struct ub_decoder *decoder, phys_addr_t addr, u64 size); +#endif /* __DECODER_H__ */ diff --git a/drivers/ub/ubus/eid.c b/drivers/ub/ubus/eid.c new file mode 100644 index 0000000000000000000000000000000000000000..74eeeeb9845b1aa0f46ec2649618efd4cddd385a --- /dev/null +++ b/drivers/ub/ubus/eid.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus eid: " fmt + +#include "ubus.h" +#include "eid.h" + +static DEFINE_IDA(ub_eid_ida); + +int ub_eid_request(guid_t *id, u32 *eid) +{ + int ida; + + if (!id || !eid) + return -EINVAL; + + ida = ida_alloc_range(&ub_eid_ida, ubc_eid_start, ubc_eid_end, GFP_KERNEL); + if (ida < 0) + return ida; + + *eid = (u32)ida; + + return 0; +} + +void ub_eid_release(u32 eid) +{ + ida_free(&ub_eid_ida, eid); +} + +int ub_eid_alloc(struct ub_entity *uent) +{ + struct device *dev; + u32 eid = 0; + int ret; + + if (is_p_device(uent)) + return 0; + + if (uent->eid) { + ub_warn(uent, "uent eid not 0, eid=%#05x\n", uent->eid); + return -EPERM; + } + + if (is_ibus_controller(uent) && uent->ubc->cluster) { + dev = &uent->ubc->dev; + ret = ub_cfg_read_dword(uent, UB_EID_0, &eid); + if (ret) { + dev_err(dev, "query cluster ubc, ret=%d\n", ret); + return ret; + } + + eid &= UB_COMPACT_EID_MASK; + if (eid) + dev_info(dev, "update cluster ubc eid, eid=%#x\n", eid); + + uent->eid = eid; + return 0; + } + + ret = ub_eid_request(&uent->guid.id, &eid); + if (!ret) + uent->eid = eid; + + return ret; +} + +void ub_eid_free(struct ub_entity *uent) +{ + u32 eid = uent->eid; + + if (is_p_device(uent)) + return; + + if (!eid) { + ub_warn(uent, "eid free 0\n"); + return; + } + + if (is_ibus_controller(uent) && uent->ubc->cluster) + return; + + uent->eid = 0; + ub_eid_release(eid); +} diff --git a/drivers/ub/ubus/eid.h b/drivers/ub/ubus/eid.h new file mode 100644 index 0000000000000000000000000000000000000000..9d71ad5cf228d2e51528746cb94e5b435a1743fd --- /dev/null +++ b/drivers/ub/ubus/eid.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __EID_H__ +#define __EID_H__ + +struct ub_entity; + +int ub_eid_request(guid_t *id, u32 *eid); +void ub_eid_release(u32 eid); +int ub_eid_alloc(struct ub_entity *uent); +void ub_eid_free(struct ub_entity *uent); + +#endif /* __EID_H__ */ diff --git a/drivers/ub/ubus/entity.c b/drivers/ub/ubus/entity.c new file mode 100644 index 0000000000000000000000000000000000000000..a288634f30c8ee7283b2a541f8b70ba7e98d837c --- /dev/null +++ b/drivers/ub/ubus/entity.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "ubus_inner.h" + +int ub_get_dst_eid(struct ub_entity *dev) +{ + struct ub_bus_controller *ubc; + u32 eid; + + if (!dev) + return -EINVAL; + + ubc = dev->ubc; + + if (!ubc) + return -ENODEV; + + eid = ubc->uent->eid; + if (dev->bi) + eid = dev->bi->info.eid; + + return eid; +} +EXPORT_SYMBOL_GPL(ub_get_dst_eid); diff --git a/drivers/ub/ubus/enum.c b/drivers/ub/ubus/enum.c new file mode 100644 index 0000000000000000000000000000000000000000..6f9b04d1e1867a6b56102b271d6033f4b17e8703 --- /dev/null +++ b/drivers/ub/ubus/enum.c @@ -0,0 +1,1481 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus enum: " fmt + +#include + +#include "ubus.h" +#include "ubus_inner.h" +#include "ubus_entity.h" +#include "msg.h" +#include "port.h" +#include "cna.h" +#include "route.h" +#include "enum.h" + +#define ENUM_MAX_HOPS 255 + +enum enum_topo_query_opcode { + ENUM_TOPO_QUERY_RESPONSE = 0, + ENUM_TOPO_QUERY_REQUEST +}; + +enum enum_na_cfg_opcode { + ENUM_NA_CFG_RESPONSE = 0, + ENUM_NA_CFG_PRIMARY = 2, + ENUM_NA_CFG_PORT = 3 +}; + +enum enum_hop_type { + ENUM_HOP_TYPE_4BIT = 0, + ENUM_HOP_TYPE_BYTE, + ENUM_HOP_TYPE_WORD +}; + +enum enum_na_query_opcode { + ENUM_NA_QUERY_RESPONSE = 0, + ENUM_NA_QUERY_SWITCH, + ENUM_NA_QUERY_DEVICE, + ENUM_NA_QUERY_PORT, +}; + +struct enum_na_cfg_req { + /* DW0~DW5 */ + struct enum_pld_scan_pdu_common common; + /* DW6 */ + u16 rsvd; + u16 port_idx; + /* DW7 */ + u32 cna : 24; + u32 rsvd1 : 8; +}; +#define ENUM_NA_CFG_REQ_SIZE 32 + +struct enum_na_cfg_rsp { + /* DW0~DW5 */ + struct enum_pld_scan_pdu_common common; +}; +#define ENUM_NA_CFG_RSP_SIZE 24 + +struct enum_na_query_req { + /* DW0~DW5 */ + struct enum_pld_scan_pdu_common common; + /* DW6 */ + u32 port_idx : 16; + u32 rsv : 16; +}; +#define ENUM_NA_QUERY_REQ_SIZE 28 + +struct enum_na_query_rsp { + /* DW0~DW5 */ + struct enum_pld_scan_pdu_common common; + /* DW6 */ + u32 cna : 24; + u32 rsvd : 8; +}; +#define ENUM_NA_QUERY_RSP_SIZE 28 + +static void ub_enum_pkt_header_init(void *buf) +{ + struct enum_pkt_header *header = (struct enum_pkt_header *)buf; + struct compact_network_header *cnth = &header->cnth; + struct ub_link_header *ulh = &header->ulh; + + ulh->cfg = UB_COMPACT_LINK_CFG; + cnth->nth_nlp = NTH_NLP_WITHOUT_TPH; + cnth->mgmt = 1; + header->upi = UB_CP_UPI; +} + +/* Attention: The caller must ensure that hop_type is valid */ +static size_t calc_forward_path_size(struct enum_pld_scan_header *header) +{ +#define FOUR_BITS_PER_DWORD 8 + u8 hop_bits[] = { SZ_4, SZ_8, SZ_16 }; + + /* Path size is hops * hop_bits[], then align it to 4byte */ + return ALIGN(hop_bits[header->bits.hop_type] * header->bits.hops / + hop_bits[0], FOUR_BITS_PER_DWORD) / SZ_2; +} + +static void set_hop_path(u8 *path, int type, u32 index, u32 val) +{ + u32 offset, mask; + u16 *p; + + switch (type) { + case ENUM_HOP_TYPE_4BIT: + offset = index / SZ_2; + mask = 0x0F << ((index % SZ_2) * SZ_4); + val = val << ((index % SZ_2) * SZ_4); + break; + case ENUM_HOP_TYPE_BYTE: + offset = index; + mask = GENMASK(7, 0); + break; + case ENUM_HOP_TYPE_WORD: + offset = index * SZ_2; + mask = GENMASK(15, 0); + break; + default: + return; + } + + p = (u16 *)(path + offset); + *p = (*p & ~mask) | (val & mask); +} + +static void ub_enum_pld_header_init(struct ub_entity *uent, void *buf) +{ + struct enum_pld_scan_header *header; + struct ub_entity *parent, *target; + struct ub_port *port; + u8 *path, *r_path; + size_t size; + int i, j; + + header = (struct enum_pld_scan_header *)(buf + ENUM_PKT_HEADER_SIZE); + header->bits.hop_type = ENUM_HOP_TYPE_BYTE; + header->bits.hops = (u32)uent->topo_rank; + header->bits.step = 0; + + if (header->bits.hops == 0) /* First hop doesn't need path */ + return; + + header->bits.r = 1; /* Use return path */ + + size = calc_forward_path_size(header); + + path = header->path; + r_path = path + size; + target = uent; + for (i = uent->topo_rank - 1; i >= 0; i--) { + parent = to_ub_entity(target->dev.parent); + for_each_uent_port(port, parent) { + if (port->r_uent == target) { + set_hop_path(path, header->bits.hop_type, + (u32)i, (u32)port->index); + j = uent->topo_rank - 1 - i; + set_hop_path(r_path, header->bits.hop_type, + (u32)j, (u32)port->r_index); + break; + } + } + target = parent; + } +} + +/* rsp should check return value, req is inside caller, don't need */ +size_t calc_enum_pld_header_size(struct enum_pld_scan_header *header, bool req) +{ + size_t bytes; + + if (!req && header->bits.hop_type > ENUM_HOP_TYPE_WORD) { + pr_err("rsp header hop_type error, type=%u\n", header->bits.hop_type); + return 0; + } + + bytes = calc_forward_path_size(header); + + if (req && header->bits.r) + bytes <<= 1; + + bytes += ENUM_PLD_SCAN_HEADER_BASE_SIZE; + + return bytes; +} +EXPORT_SYMBOL_GPL(calc_enum_pld_header_size); + +/* pkt header + scan header */ +static size_t ub_enum_calc_header_sz(void *buf, bool req) +{ + size_t bytes; + + bytes = calc_enum_pld_header_size((struct enum_pld_scan_header *)(buf + + ENUM_PKT_HEADER_SIZE), req); + if (bytes == 0) + return 0; + + return bytes + ENUM_PKT_HEADER_SIZE; +} + +static struct topo_scan_info { +#define UB_TOPO_BUF_SZ SZ_4K + void *buf; + struct list_head dev_list; +} topo_scan; + +static int ub_enum_topo_scan_init(void) +{ + topo_scan.buf = kzalloc(UB_TOPO_BUF_SZ, GFP_KERNEL); + if (!topo_scan.buf) + return -ENOMEM; + + INIT_LIST_HEAD(&topo_scan.dev_list); + + return 0; +} + +static void ub_enum_topo_scan_uninit(void) +{ + kfree(topo_scan.buf); +} + +static inline void ub_enum_refresh_and_init_buffer_header(struct ub_entity *uent, + void *buf) +{ + memset(buf, 0, UB_TOPO_BUF_SZ); + ub_enum_pkt_header_init(buf); + ub_enum_pld_header_init(uent, buf); +} + +static void ub_enum_pld_common_setup(struct enum_pld_scan_pdu_common *common, + u8 cmd, u8 opcode, guid_t *guid, + int plen) +{ + common->bits.version = UB_ENUM_MNG_VERSION; + common->bits.cmd = cmd; + common->bits.opcode = opcode; + common->msn = 0; + common->guid = *guid; + common->pdu_len = (u8)(plen / SZ_4); +} + +static int ub_enum_topo_query(struct ub_entity *uent, void *buf, u16 start_idx, + u16 *actual_rsp_size) +{ + struct message_device *mdev = uent->message->mdev; + struct enum_topo_query_req *req; + struct msg_info info = {}; + size_t header_sz; + int ret; + + ub_enum_refresh_and_init_buffer_header(uent, buf); + + header_sz = ub_enum_calc_header_sz(buf, true); + req = (struct enum_topo_query_req *)(buf + header_sz); + ub_enum_pld_common_setup(&req->common, ENUM_CMD_TOPO_QUERY, + ENUM_TOPO_QUERY_REQUEST, &uent->guid.id, + ENUM_TOPO_QUERY_REQ_SIZE); + + req->common.bits.slice_id = start_idx; + + message_info_init(&info, uent, buf, buf, + ((header_sz + ENUM_TOPO_QUERY_REQ_SIZE) << + MSG_REQ_SIZE_OFFSET) | UB_TOPO_BUF_SZ); + + ret = message_sync_enum(mdev, &info, ENUM_CMD_TOPO_QUERY); + if (!ret) + *actual_rsp_size = info.actual_rsp_size; + + return ret; +} + +static struct enum_topo_query_rsp * +ub_enum_topo_query_and_check(struct ub_entity *uent, void *buf, u16 start_idx) +{ + struct device *dev = &uent->ubc->dev; + struct enum_topo_query_rsp *rsp; + u16 actual_rsp_size; + size_t header_sz; + int ret; + + ret = ub_enum_topo_query(uent, buf, start_idx, &actual_rsp_size); + if (ret) { + dev_err(dev, "enum topo query failed, ret=%d\n", ret); + return NULL; + } + + header_sz = ub_enum_calc_header_sz(buf, false); + if (header_sz == 0) { + dev_err(dev, "enum topo query rsp header sz 0\n"); + return NULL; + } + + if (header_sz + ENUM_TOPO_QUERY_RSP_BASE_SIZE > actual_rsp_size) { + dev_err(dev, "enum topo query rsp pkt size invalid\n"); + return NULL; + } + + rsp = (struct enum_topo_query_rsp *)(buf + header_sz); + ret = rsp->common.bits.status; + if (ret) { + dev_err(dev, "enum rsp has error, status=%d\n", ret); + return NULL; + } + + if (header_sz + rsp->common.pdu_len * SZ_4 != actual_rsp_size) { + dev_err(dev, "enum topo query rsp pdu_len invalid\n"); + return NULL; + } + + return rsp; +} + +static void ub_enum_parse_port(struct ub_entity *uent, + struct enum_tlv_port_info *pi, u16 num) +{ + struct enum_tlv_port_info *port_info; + struct device *dev = &uent->ubc->dev; + struct ub_port *port; + + for (u16 i = 0; i < num; i++) { + port_info = pi + i; + if (port_info->local_port_idx >= uent->port_nums) { + dev_err(dev, "local port idx %u exceeds uent port num %u\n", + port_info->local_port_idx, uent->port_nums); + continue; + } + + port = uent->ports + port_info->local_port_idx; + + port->domain_boundary = port_info->bits0.b; + port->type = port_info->bits0.t ? VIRTUAL : PHYSICAL; + port->shareable = (bool)port_info->bits0.w; + + /* skip link down port */ + if (!port_info->bits0.s) + continue; + + if (!is_device(uent) && !is_idev(uent) && port->domain_boundary) + continue; + + /* neighbor info of a boundary port shouldn't be stored */ + port->r_index = port_info->remote_port_idx; + guid_copy(&port->r_guid, &port_info->remote_guid); + } +} + +struct enum_tlv_info { + struct enum_tlv_slice_info *si; + struct enum_tlv_port_info *pi; + struct enum_tlv_port_num *pn; + struct enum_tlv_cap_info *ci; + u16 port_nums; +}; + +static int ub_enum_tlv_parse(void *tlv, int tlv_len, struct enum_tlv_info *info) +{ + struct enum_tlv_common *common; + u16 port_nums = 0; + int left_len = tlv_len; + + while ((left_len > 0) && (left_len % SZ_4 == 0)) { + common = (struct enum_tlv_common *)tlv; + + switch (common->type) { + case TLV_SLICE_INFO: + info->si = (struct enum_tlv_slice_info *)tlv; + break; + case TLV_PORT_NUM: + info->pn = (struct enum_tlv_port_num *)tlv; + break; + case TLV_PORT_INFO: + if (port_nums == 0) + info->pi = (struct enum_tlv_port_info *)tlv; + port_nums++; + break; + case TLV_CAP_INFO: + info->ci = (struct enum_tlv_cap_info *)tlv; + break; + default: + pr_warn("not support tlv type[%u]\n", common->type); + } + + if (common->len == 0) { + pr_err("enum tlv len is invalid\n"); + return -EINVAL; + } + + left_len -= (int)common->len; + tlv += common->len; + } + + info->port_nums = port_nums; + + return 0; +} + +/* only check guid itself here, repetition problem is not considered */ +bool ub_type_valid(struct ub_entity *uent, bool is_ctl) +{ + struct device *dev = &uent->ubc->dev; + u16 code = uent_class(uent); + u8 type = uent_type(uent); + + if (uent->ent_type == UB_ENT_UNKNOWN) { + dev_err(dev, "ent type unknown, type=%#x, class=%#x\n", + type, code); + return false; + } + + if (is_ctl && !is_ibus_controller(uent)) { + dev_err(dev, "ubc bad type, type=%#x, class=%#x\n", + type, code); + return false; + } + + if (!is_ctl && (is_ibus_controller(uent) || is_bus_controller(uent))) { + dev_err(dev, "peripheral bad type, type=%#x, class=%#x\n", + type, code); + return false; + } + + if (uent->pool && !(is_p_device(uent) || is_p_idevice(uent))) { + dev_err(dev, "pool bad type, type=%#x, class=%#x\n", + type, code); + return false; + } + + return true; +} + +void ub_entity_type_init(struct ub_entity *uent) +{ + u8 base = uent_base_code(uent); + + switch (uent_type(uent)) { + case UB_TYPE_CONTROLLER: + if (base == UB_BASE_CODE_BUS_CONTROLLER) { + if (uent->class_code == UB_CLASS_BUS_CONTROLLER) + uent->ent_type = UB_ENT_BUS_CONTROLLER; + else + uent->ent_type = UB_ENT_UNKNOWN; + } else { + if (!uent->pool) + uent->ent_type = UB_ENT_DEVICE; + else + uent->ent_type = UB_ENT_P_DEVICE; + } + break; + case UB_TYPE_ICONTROLLER: + if (base == UB_BASE_CODE_BUS_CONTROLLER) { + uent->ent_type = UB_ENT_IBUS_CONTROLLER; + } else { + if (!uent->pool) + uent->ent_type = UB_ENT_IDEVICE; + else + uent->ent_type = UB_ENT_P_IDEVICE; + } + break; + case UB_TYPE_SWITCH: + case UB_TYPE_ISWITCH: + uent->ent_type = UB_ENT_SWITCH; + break; + default: + uent->ent_type = UB_ENT_UNKNOWN; + } +} + +#define PORT_TOTAL_NUM_MAX 256 + +static int ub_enum_ent(struct ub_entity *uent, void *buf) +{ + struct device *dev = &uent->ubc->dev; + struct enum_pld_scan_pdu_common *pc; + int tlv_len, ret, total_num_ports; + struct enum_tlv_info info = {}; + u8 total_slice, slice_id = 0; + void *rsp, *tlv; + + do { + rsp = (void *)ub_enum_topo_query_and_check(uent, buf, slice_id); + if (!rsp) { + ret = -EBUSY; + goto err; + } + + pc = (struct enum_pld_scan_pdu_common *)rsp; + tlv_len = (int)pc->pdu_len * SZ_4 - ENUM_PLD_SCAN_PDU_COMMON_SIZE; + tlv = rsp + ENUM_PLD_SCAN_PDU_COMMON_SIZE; + + ret = ub_enum_tlv_parse(tlv, tlv_len, &info); + if (ret) + goto err; + + if (slice_id == 0) { + if (!info.si || !info.pi || !info.pn || !info.ci) { + dev_err(dev, "tlv si/pi/pn/ci NULL\n"); + ret = -EFAULT; + goto err; + } + + uent->class_code = info.ci->class_code; + ub_entity_type_init(uent); + if (!ub_type_valid(uent, !uent->topo_rank)) { + ret = -EINVAL; + goto err; + } + + total_slice = info.si->total_slice; + total_num_ports = (int)info.pn->total_num_ports; + } + + if (!uent->ports) { + uent->port_nums = total_num_ports; + if (uent->port_nums > PORT_TOTAL_NUM_MAX) { + dev_err(dev, "Total num ports is over total num max(%d).\n", + PORT_TOTAL_NUM_MAX); + return -EINVAL; + } + + ret = ub_ports_setup(uent); + if (ret) { + dev_err(dev, "enum port setup fail, ret=%d\n", + ret); + return ret; + } + } + + ub_enum_parse_port(uent, info.pi, info.port_nums); + slice_id++; + } while (slice_id < total_slice); + + return 0; +err: + if (uent->ports) + ub_ports_unset(uent); + return ret; +} + +static int ub_enum_na_query_rsp(void *buf, u32 *cna, u16 actual_rsp_size) +{ + struct enum_na_query_rsp *rsp; + size_t header_sz; + + header_sz = ub_enum_calc_header_sz(buf, false); + if (header_sz == 0) { + pr_err("na query rsp header sz 0\n"); + return -EINVAL; + } + + if (header_sz + ENUM_NA_QUERY_RSP_SIZE != actual_rsp_size) { + pr_err("na query rsp pld sz invalid\n"); + return -EINVAL; + } + + rsp = (struct enum_na_query_rsp *)(buf + header_sz); + + if (!rsp->common.bits.status) + *cna = rsp->cna; + + return rsp->common.bits.status; +} + +static size_t ub_enum_na_query_init(struct ub_entity *uent, void *buf, u8 opcode, + u16 port_idx) +{ + struct enum_na_query_req *req; + size_t header_sz; + + memset(buf, 0, UB_TOPO_BUF_SZ); + ub_enum_pkt_header_init(buf); + ub_enum_pld_header_init(uent, buf); + + header_sz = ub_enum_calc_header_sz(buf, true); + req = (struct enum_na_query_req *)(buf + header_sz); + + ub_enum_pld_common_setup(&req->common, ENUM_CMD_NA_QUERY, + opcode, &uent->guid.id, + ENUM_NA_QUERY_REQ_SIZE); + + if (opcode == ENUM_NA_QUERY_PORT) + req->port_idx = port_idx; + + return header_sz + ENUM_NA_QUERY_REQ_SIZE; +} + +int ub_query_port_na(struct ub_entity *uent, void *buf) +{ + struct device *dev = &uent->ubc->dev; + struct msg_info info = {0}; + size_t req_pkt_sz; + struct ub_port *p; + int ret; + u32 cna; + + for_each_uent_port(p, uent) { + if (!p->domain_boundary) + continue; + + req_pkt_sz = ub_enum_na_query_init(uent, buf, + ENUM_NA_QUERY_PORT, + p->index); + + message_info_init(&info, uent, buf, buf, + (req_pkt_sz << MSG_REQ_SIZE_OFFSET) | + UB_TOPO_BUF_SZ); + + ret = message_sync_enum(uent->ubc->mdev, &info, + ENUM_CMD_NA_QUERY); + if (ret) + goto out; + + ret = ub_enum_na_query_rsp(buf, &cna, info.actual_rsp_size); + if (ret) { + dev_err(dev, "port na query rsp, status=%d\n", ret); + ret = -EBUSY; + goto out; + } + + p->cna = cna; + if (p->cna) + dev_info(dev, "update boundary port%u cna to %#x\n", + p->index, p->cna); + } + + return 0; +out: + for_each_uent_port(p, uent) + if (p->domain_boundary) + p->cna = 0; + + dev_err(dev, "query fail and reset boundary port cna to 0.\n"); + + return ret; +} + +int ub_query_ent_na(struct ub_entity *uent, void *buf) +{ + struct device *dev = &uent->ubc->dev; + struct msg_info info = {}; + size_t req_pkt_sz; + int ret; + u32 cna; + + req_pkt_sz = ub_enum_na_query_init(uent, buf, ENUM_NA_QUERY_DEVICE, 0); + message_info_init(&info, uent, buf, buf, + (req_pkt_sz << MSG_REQ_SIZE_OFFSET) | + UB_TOPO_BUF_SZ); + + ret = message_sync_enum(uent->ubc->mdev, &info, + ENUM_CMD_NA_QUERY); + if (ret) + return ret; + + ret = ub_enum_na_query_rsp(buf, &cna, info.actual_rsp_size); + if (ret) { + dev_err(dev, "dev na query rsp, status=%d\n", ret); + return -EBUSY; + } + + uent->cna = cna; + if (uent->cna) + dev_info(dev, "update cluster ubc cna to %#x\n", uent->cna); + + return 0; +} + +static int ub_enum_cluster_query(struct ub_entity *uent, void *buf) +{ + int ret; + + ret = ub_query_ent_na(uent, buf); + if (ret) { + dev_err(&uent->ubc->dev, "query cluster ubc dev cna err\n"); + return ret; + } + + ret = ub_query_port_na(uent, buf); + if (ret) + dev_err(&uent->ubc->dev, "query cluster ubc port cna err\n"); + + return ret; +} + +static int ub_enum_na_cfg_rsp(void *buf, u16 actual_rsp_size) +{ + struct enum_na_cfg_rsp *rsp; + size_t header_sz; + + header_sz = ub_enum_calc_header_sz(buf, false); + if (header_sz == 0) { + pr_err("na cfg rsp header sz 0\n"); + return -EINVAL; + } + + if (header_sz + ENUM_NA_CFG_RSP_SIZE != actual_rsp_size) { + pr_err("na cfg rsp pld sz invalid\n"); + return -EINVAL; + } + + rsp = (struct enum_na_cfg_rsp *)(buf + header_sz); + + return rsp->common.bits.status; +} + +static size_t ub_enum_na_cfg_init(struct ub_entity *uent, void *buf, u32 cna, + u8 opcode, u16 port_idx) +{ + struct enum_na_cfg_req *req; + size_t header_sz; + + ub_enum_refresh_and_init_buffer_header(uent, buf); + + header_sz = ub_enum_calc_header_sz(buf, true); + req = (struct enum_na_cfg_req *)(buf + header_sz); + + ub_enum_pld_common_setup(&req->common, ENUM_CMD_NA_CFG, opcode, + &uent->guid.id, ENUM_NA_CFG_REQ_SIZE); + + req->cna = cna; + if (opcode == ENUM_NA_CFG_PORT) + req->port_idx = port_idx; + + return header_sz + ENUM_NA_CFG_REQ_SIZE; +} + +/* Configuring the CNA of the ub entity Port */ +static int ub_enum_port_na_cfg(struct ub_entity *uent, void *buf) +{ + struct message_device *mdev = uent->message->mdev; + struct msg_info info = {}; + size_t req_pkt_sz; + struct ub_port *p; + int ret; + + for_each_uent_port(p, uent) { + if (is_ibus_controller(uent) && p->domain_boundary) + continue; + + req_pkt_sz = ub_enum_na_cfg_init(uent, buf, p->cna, + ENUM_NA_CFG_PORT, p->index); + + message_info_init(&info, uent, buf, buf, + (req_pkt_sz << MSG_REQ_SIZE_OFFSET) | + UB_TOPO_BUF_SZ); + + ret = message_sync_enum(mdev, &info, ENUM_CMD_NA_CFG); + if (ret) + return ret; + + ret = ub_enum_na_cfg_rsp(buf, info.actual_rsp_size); + if (ret) { + dev_err(&uent->ubc->dev, "port na cfg rsp, status=%d\n", + ret); + return -EBUSY; + } + } + + return 0; +} + +static int ub_enum_na_cfg(struct ub_entity *uent, void *buf) +{ + struct message_device *mdev = uent->message->mdev; + struct device *dev = &uent->ubc->dev; + enum enum_na_cfg_opcode opcode; + struct msg_info info = {}; + size_t req_pkt_sz; + int ret; + + ret = ub_cna_alloc(uent); + if (ret) { + dev_err(dev, "cna alloc fail, ret=%d\n", ret); + goto na_failed; + } + + if (is_switch(uent)) /* Switch hasn't port cna register */ + goto cfg_dev; + + ret = ub_enum_port_na_cfg(uent, buf); + if (ret) { + dev_err(dev, "port na cfg fail, ret=%d\n", ret); + goto na_failed; + } + + if (is_ibus_controller(uent) && uent->ubc->cluster) { + ret = ub_enum_cluster_query(uent, buf); + if (ret) + goto na_failed; + return 0; + } + +cfg_dev: + opcode = ENUM_NA_CFG_PRIMARY; + req_pkt_sz = ub_enum_na_cfg_init(uent, buf, uent->cna, opcode, 0); + + message_info_init(&info, uent, buf, buf, + (req_pkt_sz << MSG_REQ_SIZE_OFFSET) | UB_TOPO_BUF_SZ); + + ret = message_sync_enum(mdev, &info, ENUM_CMD_NA_CFG); + if (ret) + goto na_failed; + + return 0; + +na_failed: + ub_cna_free(uent); + return ret; +} + + +/* + * Routing Algorithm: The BFS (Breadth First Search) algorithm + * calculates the shortest forwarding path. The steps are as follows: + * 1. Obtain the "nodes" and forwarding device information in the host + * scenario, and construct an interconnected network in the single host + * scenario. + * 2. Starting from each node in the interconnected network, sequentially + * visit the adjacent nodes that are directly connected with one hop, + * without considering weight/cost. + * 3. Visit the adjacent nodes of the adjacent nodes, traversing layer by + * layer. + * 4. Continue until reaching the destination or there are no unvisited nodes + * left, constructing the forwarding path with the minimum number of hops. + * 5. When the weights/costs of the links are not the same, the BFS algorithm + * may calculate non-shortest paths. + * 6. Based on the above mechanism, the BFS algorithm does not allow cycles. + */ + +struct bfs_port_path { + struct ub_port *s_port; /* source port for set cna_map */ + struct ub_entity *uent; /* BFS current ub entity */ +}; + +static struct bfs_route_info { + DECLARE_KFIFO_PTR(kfifo, struct bfs_port_path); + int *visited; /* 0: unvisited, 1: visited, 2: visiting */ + u32 cna_used; + u32 *cna_map; +} bfs_route; + +enum { BFS_UNVISITED, BFS_VISITED, BFS_VISITING }; + +/* Return: Whether to join the kfifo */ +static int bfs_route_test_and_put(struct ub_port *p, struct ub_entity *uent) +{ + struct bfs_port_path path; + + if (!is_switch(uent) && !is_ibus_controller(uent)) + return 0; + + if (uent->port_nums == 1) + return 0; + + path.s_port = p; + path.uent = uent; + + if (!kfifo_put(&bfs_route.kfifo, path)) { + pr_info("multiple paths join the kfifo, kfifo put failed\n"); + return 0; + } + + return 1; +} + +static int cna2index(int cna) +{ + int i; + + for (i = 0; i < bfs_route.cna_used; ++i) { + if (bfs_route.cna_map[i] == cna) + return i; + } + + return 0; +} + +static void ub_enum_bfs_layer_update(struct ub_entity *uent, int *curr, int *next, + int *layer) +{ + int i; + + *curr = *next; + *next = 0; + *layer += 1; + + for (i = 0; i < bfs_route.cna_used; ++i) + if (bfs_route.visited[i] == BFS_VISITING) + bfs_route.visited[i] = BFS_VISITED; +} + +static void ub_enum_bfs_route_dev(struct ub_entity *ldev) +{ + struct ub_entity *uent, *rdev; + struct ub_port *p, *rport, *s_port; + struct bfs_port_path path = { NULL, ldev }; + int curr_layer_devs = 1, next_layer_devs = 0, layer = 0; + + bfs_route.visited[cna2index(ldev->cna)] = BFS_VISITED; + kfifo_put(&bfs_route.kfifo, path); + while (kfifo_get(&bfs_route.kfifo, &path)) { + curr_layer_devs--; + uent = path.uent; + for_each_uent_port(p, uent) { + if (!p->cna || !p->r_uent || + bfs_route.visited[cna2index(p->r_uent->cna)] == + BFS_VISITED || + !ub_entity_test_priv_flag(p->r_uent, UB_ENTITY_DETACHED)) + continue; + + rdev = p->r_uent; + rport = rdev->ports + p->r_index; + s_port = path.s_port ? path.s_port : p; + + bfs_route.visited[cna2index(rdev->cna)] = BFS_VISITING; + ub_route_add_entry(s_port, rdev->cna, layer + 1); + if (!ONE_CNA(rdev)) + ub_route_add_entry(s_port, rport->cna, layer + 1); + ub_entity_assign_priv_flag(ldev, UB_ENTITY_ROUTE_UPDATED, true); + next_layer_devs += bfs_route_test_and_put(s_port, rdev); + } + + if (curr_layer_devs == 0) + ub_enum_bfs_layer_update(uent, &curr_layer_devs, + &next_layer_devs, &layer); + } +} + +static void ub_enum_bfs_route_core(struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry(uent, dev_list, node) { + if (uent->port_nums == 1) + continue; + + memset(bfs_route.visited, 0, sizeof(int) * bfs_route.cna_used); + + ub_enum_bfs_route_dev(uent); + } +} + +static void ub_bfs_set_cna_map(struct list_head *dev_list) +{ + struct ub_port *port; + struct ub_entity *uent; + int i = 0; + + list_for_each_entry(uent, dev_list, node) { + bfs_route.cna_map[i++] = uent->cna; + if (ONE_CNA(uent)) + continue; + for_each_uent_port(port, uent) + bfs_route.cna_map[i++] = port->cna; + } +} + +int ub_enum_bfs_route_cal(struct list_head *dev_list) +{ + u16 max_port_num = 0; + struct ub_entity *uent; + int ret = -ENOMEM; + + if (kfifo_alloc(&bfs_route.kfifo, SZ_4K, GFP_KERNEL)) + goto kfifo_fail; + + bfs_route.cna_used = 0; + list_for_each_entry(uent, dev_list, node) { + if (max_port_num < uent->port_nums) + max_port_num = uent->port_nums; + bfs_route.cna_used += ONE_CNA(uent) ? 1 : uent->port_nums + 1; + } + + bfs_route.cna_map = kcalloc(bfs_route.cna_used, sizeof(u32), GFP_KERNEL); + if (!bfs_route.cna_map) + goto map_fail; + ub_bfs_set_cna_map(dev_list); + + bfs_route.visited = kcalloc(bfs_route.cna_used, sizeof(int), GFP_KERNEL); + if (!bfs_route.visited) + goto visited_fail; + + ub_enum_bfs_route_core(dev_list); + ret = 0; + + kfree(bfs_route.visited); +visited_fail: + kfree(bfs_route.cna_map); +map_fail: + kfifo_free(&bfs_route.kfifo); +kfifo_fail: + return ret; +} + +int ub_cfg_read_guid(struct ub_entity *uent, u32 *dw) +{ + u32 val[UB_GUID_DW_NUM]; + int i, ret; + + for (i = 0; i < UB_GUID_DW_NUM; i++) { + ret = ub_cfg_read_dword(uent, UB_GUID + i * sizeof(u32), &val[i]); + if (ret) + return ret; + } + + for (i = 0; i < UB_GUID_DW_NUM; i++) + *(dw + i) = val[i]; + + return 0; +} + +static void ub_enum_destroy_entity(struct ub_entity *uent) +{ + message_remove_device(uent); + + if (is_primary(uent)) + ub_ubc_put(uent->ubc); + + kfree(uent); +} + +static struct ub_entity *ub_enum_create_uent(struct ub_bus_controller *ubc) +{ + struct ub_entity *uent; + int ret; + + uent = ub_alloc_ent(); + if (!uent) + return NULL; + + uent->pue = uent; + uent->entity_idx = 0; + uent->is_mue = 1; + uent->ubc = ub_ubc_get(ubc); + uent->upi = UB_CP_UPI; + ret = message_probe_device(uent); + if (ret) { + dev_err(&ubc->dev, "enum msg probe dev failed, ret=%d\n", ret); + goto err_out; + } + + return uent; +err_out: + ub_ubc_put(ubc); + kfree(uent); + return NULL; +} + +static struct ub_entity *ub_enum_create_bus_controller(struct ub_bus_controller *ubc) +{ + struct ub_entity *uent; + int ret; + + uent = ub_enum_create_uent(ubc); + if (!uent) + return (struct ub_entity *)ERR_PTR(-ENOMEM); + + ubc->uent = uent; + ret = ub_cfg_read_guid(uent, uent->guid.dw); + if (ret) { + dev_err(&ubc->dev, "read guid failed, ret=%d\n", ret); + goto err_out; + } + + uent->topo_rank = 0; + uent->dev.parent = &ubc->dev; + + return uent; +err_out: + ub_enum_destroy_entity(uent); + return (struct ub_entity *)ERR_PTR(ret); +} + +static void ub_enum_topo_ent_uninit(struct ub_entity *uent) +{ + if (is_primary(uent)) { + ub_route_clear(uent); /* alloc in route_config */ + ub_cna_free(uent); /* alloc in na_cfg */ + ub_ports_unset(uent); /* alloc in scan_device */ + } +} + +static int ub_enum_ports(struct ub_entity *uent, u16 start_port, u16 num, + void *buf) +{ + u16 left = num, start = start_port, count; + struct enum_pld_scan_pdu_common *pc; + struct enum_tlv_info info = {}; + u8 total_slice, slice_id = 0; + int tlv_len, ret, nums = 0; + void *rsp, *tlv; + + do { + rsp = (void *)ub_enum_topo_query_and_check(uent, buf, slice_id); + if (!rsp) + return -EBUSY; + + pc = rsp; + tlv_len = (int)pc->pdu_len * SZ_4 - ENUM_PLD_SCAN_PDU_COMMON_SIZE; + tlv = rsp + ENUM_PLD_SCAN_PDU_COMMON_SIZE; + + ret = ub_enum_tlv_parse(tlv, tlv_len, &info); + if (ret) + return ret; + + if (slice_id == 0) { + if (!info.si || !info.pi || !info.pn || !info.ci) { + dev_err(&uent->ubc->dev, "port tlv si/pi/pn/ci null\n"); + return -EINVAL; + } + + total_slice = info.si->total_slice; + } + + if (start >= nums + info.port_nums) + goto next; + + info.pi += start - nums; + count = min(left, nums + info.port_nums - start); + ub_enum_parse_port(uent, info.pi, count); + start += count; + left -= count; + + if (left == 0) + break; +next: + nums += info.port_nums; + slice_id++; + } while (slice_id < total_slice); + + return 0; +} + +static int ub_enum_and_configure_ent(struct ub_entity *uent, void *buf) +{ + int ret; + + ret = ub_enum_ent(uent, buf); + if (ret) + return ret; + + ret = ub_enum_na_cfg(uent, buf); + if (ret) + ub_ports_unset(uent); + + return ret; +} + +void ub_enum_clear_ent_list(struct list_head *dev_list) +{ + struct ub_entity *uent, *tmp; + struct ub_port *port; + + list_for_each_entry_safe_reverse(uent, tmp, dev_list, node) { + list_del(&uent->node); + for_each_uent_port(port, uent) + ub_port_disconnect(port); + + ub_cna_free(uent); + ub_ports_unset(uent); + ub_enum_destroy_entity(uent); + } +} + +static int ub_enum_bus_controllers(struct list_head *dev_list) +{ + struct ub_bus_controller *ubc; + struct ub_entity *uent; + int ret; + + list_for_each_entry(ubc, &ubc_list, node) { + uent = ub_enum_create_bus_controller(ubc); + if (IS_ERR(uent)) { + ret = PTR_ERR(uent); + dev_err(&ubc->dev, "create controller failed, ret=%d\n", + ret); + goto clear_list; + } + + ret = ub_enum_and_configure_ent(uent, topo_scan.buf); + if (ret) { + dev_err(&ubc->dev, "enum controller failed, ret=%d\n", + ret); + ub_enum_destroy_entity(uent); + goto clear_list; + } + + list_add_tail(&uent->node, dev_list); + } + + return 0; +clear_list: + ub_enum_clear_ent_list(dev_list); + return ret; +} + +static struct ub_entity *ub_enum_get_ent(guid_t *guid, struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry(uent, dev_list, node) + if (guid_equal(guid, &uent->guid.id)) + return uent; + + return NULL; +} + +static bool ub_enum_port_recognise_check(struct ub_entity *root, + struct ub_port *port) +{ + struct ub_port *tmp; + + if (guid_equal(&port->r_guid, &root->guid.id)) { + tmp = root->ports + port->r_index; + if (guid_is_null(&tmp->r_guid)) { + port->r_index = 0; + guid_copy(&port->r_guid, &guid_null); + return true; + } + } + + return false; +} + +static bool port_need_scan(struct ub_entity *root, struct ub_port *port) +{ + if (port->r_uent) + return false; + + if (guid_is_null(&port->r_guid)) + return false; + + if (root && ub_enum_port_recognise_check(root, port)) + return false; + + return true; +} + +static struct ub_entity *ub_enum_create_entity(guid_t *guid, struct ub_entity *parent) +{ + struct ub_entity *uent; + + if (parent->topo_rank + 1 > ENUM_MAX_HOPS) { + dev_err(&parent->ubc->dev, "ub support max hops: %d\n", + ENUM_MAX_HOPS); + return (struct ub_entity *)ERR_PTR(-EINVAL); + } + + uent = ub_enum_create_uent(parent->ubc); + if (!uent) + return (struct ub_entity *)ERR_PTR(-ENOMEM); + + guid_copy(&uent->guid.id, guid); + + uent->topo_rank = parent->topo_rank + 1; + uent->dev.parent = &parent->dev; + + return uent; +} + +static int ub_enum_do_topo_scan(struct ub_entity *root, struct list_head *dev_list, + void *buf) +{ +#define UB_TOPO_KFIFO_DEPTH SZ_128 + DECLARE_KFIFO(kfifo, struct ub_entity *, UB_TOPO_KFIFO_DEPTH); + struct ub_entity *uent, *r_uent; + struct ub_port *port; + struct device *dev; + int ret = 0; + + INIT_KFIFO(kfifo); + + if (root) + kfifo_put(&kfifo, root); + + list_for_each_entry(uent, dev_list, node) + kfifo_put(&kfifo, uent); + + while (kfifo_get(&kfifo, &uent)) { + dev = &uent->ubc->dev; + + for_each_uent_port(port, uent) { + if (!port_need_scan(root, port)) + continue; + + r_uent = ub_enum_get_ent(&port->r_guid, dev_list); + if (r_uent) + goto connect_port; + + if (is_device(uent)) { + ret = -EINVAL; + dev_err(dev, "device connect to multi peer\n"); + goto clear_list; + } + + r_uent = ub_enum_create_entity(&port->r_guid, uent); + if (IS_ERR(r_uent)) { + ret = PTR_ERR(r_uent); + dev_err(dev, "enum create device failed, ret=%d\n", + ret); + goto clear_list; + } + + /* set here to help enum find path */ + port->r_uent = r_uent; + ret = ub_enum_and_configure_ent(r_uent, buf); + if (ret) { + dev_err(dev, "enum device failed, ret=%d\n", + ret); + ub_enum_destroy_entity(r_uent); + goto clear_port; + } + + list_add_tail(&r_uent->node, dev_list); + if (!kfifo_put(&kfifo, r_uent)) + dev_err(dev, "do topo scan kfifo put full\n"); + +connect_port: + if (!ub_check_and_connect(port, r_uent)) { + dev_err(dev, "port%u wrong topo\n", port->index); + ret = -EINVAL; + goto clear_port; + } + } + } + + return 0; +clear_port: + port->r_uent = NULL; +clear_list: + ub_enum_clear_ent_list(dev_list); + return ret; +} + +int ub_enum_topo_scan_ports(struct ub_entity *uent, u16 start_port, u16 num, + struct list_head *dev_list, void *buf) +{ + int ret; + + if (!uent || !dev_list || !buf) + return -EINVAL; + + ret = ub_enum_ports(uent, start_port, num, buf); + if (ret) + return ret; + + return ub_enum_do_topo_scan(uent, dev_list, buf); +} + +struct ub_entity *ub_enum_get_port_r_uent(struct ub_port *port, void *buf) +{ + struct ub_bus_controller *ubc; + int ret; + + if (!port || !buf) + return NULL; + + ret = ub_enum_ports(port->uent, port->index, 1, buf); + if (ret) + return NULL; + + ubc = port->uent->ubc; + return ub_enum_get_ent(&port->r_guid, &ubc->devs); +} + +/* + * During topo scan, just alloc ub_entity, alloc ub_port, alloc cna, + * so, when topo scan failed, just go to free devs in topo_scan.uents + */ +static int ub_enum_topo_scan(struct list_head *dev_list) +{ + int ret; + + ret = ub_enum_topo_scan_init(); + if (ret) { + pr_err("enum topo scan init failed, ret=%d\n", ret); + return ret; + } + + ret = ub_enum_bus_controllers(dev_list); + if (ret) { + pr_err("enum create controllers failed, ret=%d\n", ret); + goto out; + } + + if (list_empty(dev_list)) { + pr_warn("No ub bus controller exists in the current environment.\n"); + ret = -ENODEV; + goto out; + } + + ret = ub_enum_do_topo_scan(NULL, dev_list, topo_scan.buf); + if (ret) + pr_err("enum scan devices failed, ret=%d\n", ret); + +out: + ub_enum_topo_scan_uninit(); + return ret; +} + +static void ub_enum_free_all(void) +{ + struct ub_entity *uent, *tmp; + + list_for_each_entry_safe_reverse(uent, tmp, &topo_scan.dev_list, node) { + list_del(&uent->node); + ub_enum_topo_ent_uninit(uent); + ub_enum_destroy_entity(uent); + } + + ub_stop_entities(); + ub_remove_entities(); +} + +int ub_enum_entities_active(struct list_head *dev_list) +{ + struct ub_entity *uent, *tmp; + int ret; + + list_for_each_entry_safe(uent, tmp, dev_list, node) { + if (uent->entity_idx != 0) + continue; + + ub_route_sync_dev(uent); + ret = ub_setup_ent(uent); + if (ret) { + pr_err("setup dev err, ret=%d\n", ret); + return ret; + } + + list_del(&uent->node); + ub_entity_add(uent, uent->ubc); + + if (is_ibus_controller(uent) && uent->ubc->cluster) + continue; + + ub_start_ent(uent); + } + + return 0; +} + +int ub_enum_probe(void) +{ + int ret; + + ret = ub_enum_topo_scan(&topo_scan.dev_list); + if (ret == -ENODEV) { + return 0; + } else if (ret) { + pr_err("topo_scan failed, ret=%d\n", ret); + return ret; + } + + ret = ub_enum_bfs_route_cal(&topo_scan.dev_list); + if (ret) { + pr_err("route cal failed, ret=%d\n", ret); + goto err_out; + } + + ret = ub_enum_entities_active(&topo_scan.dev_list); + if (ret) { + pr_err("devices start failed, ret=%d\n", ret); + goto err_out; + } + + return 0; +err_out: + ub_enum_free_all(); + return ret; +} + +void ub_enum_remove(void) +{ + ub_stop_entities(); + ub_remove_entities(); +} diff --git a/drivers/ub/ubus/enum.h b/drivers/ub/ubus/enum.h new file mode 100644 index 0000000000000000000000000000000000000000..32094ce41751c929511f951dc1893b48845e6f6f --- /dev/null +++ b/drivers/ub/ubus/enum.h @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __ENUM_H__ +#define __ENUM_H__ + +enum enum_cmd { + ENUM_CMD_TOPO_QUERY = 0, + ENUM_CMD_NA_CFG, + ENUM_CMD_NA_QUERY +}; + +/* + * enum pkt : enum_pkt_header + enum_pld_scan_header + reqX + * reqX : enum_topo_query_req or enum_na_cfg_req or enum_na_query_req + */ +struct enum_pkt_header { + /* DW0 */ + struct ub_link_header ulh; + /* DW1-DW2 */ + struct compact_network_header cnth; + /* DW3 */ + u16 rsv; + u16 upi; + + /* DW4~ */ + char payload[]; +}; +#define ENUM_PKT_HEADER_SIZE 16 + +struct enum_pld_scan_header { + /* DW0 */ + union { + struct { + u32 step : 8; + u32 hops : 8; + u32 hop_type : 4; + u32 r : 1; + u32 rsv : 11; + } bits; + u32 dw0; + }; + /* DW1~ */ + u8 path[]; /* include forward & return, 4byte align */ +}; +#define ENUM_PLD_SCAN_HEADER_BASE_SIZE 4 /* exclusive path */ + +struct enum_pld_scan_pdu_common { + /* DW0 */ + union { + struct { + union { + u8 status; + u8 slice_id; + }; + u8 opcode; + u8 cmd; +#define UB_ENUM_MNG_VERSION 0x1 + u8 version; + } bits; + u32 dw0; + }; + /* DW1 */ + u32 msn : 16; + u32 pdu_len : 8; + u32 msgq_id : 8; + /* DW2~DW5 */ + guid_t guid; +}; +#define ENUM_PLD_SCAN_PDU_COMMON_SIZE 24 + +struct enum_topo_query_req { + /* DW0~DW5 */ + struct enum_pld_scan_pdu_common common; +}; +#define ENUM_TOPO_QUERY_REQ_SIZE 24 + +struct enum_tlv_port_info { + /* DW0 */ + union { + struct { + u32 rsvd : 8; + u32 s : 1; + u32 b : 1; + u32 w : 1; + u32 t : 1; + u32 rsvd1 : 4; + u32 len : 8; + u32 type : 8; + } bits0; + u32 dw0; + }; + /* DW1 */ + u16 remote_port_idx; + u16 local_port_idx; + + /* DW2 */ + u16 cur_rate; + u16 max_rate; + + /* DW3~DW6 */ + guid_t remote_guid; +}; +#define ENUM_TOPO_QUERY_RSP_PORT_SIZE 28 + +struct enum_topo_query_rsp { + /* DW0~DW5 */ + struct enum_pld_scan_pdu_common common; + /* DW6 */ + union { + struct { + u32 num_seg : 4; + u32 rsv0 : 4; + u32 lf : 1; + u32 lp : 1; + u32 rsv1 : 6; + u32 mtu : 3; + u32 rsv2 : 5; + u32 sup_mtu : 3; + u32 rsv3 : 5; + } bits; + u32 dw6; + }; + /* DW7 */ + u32 num_lf_entries; + /* DW8 */ + u32 num_lp_entries; + /* DW9 */ + u16 num_ports; /* Number of ports in the current message */ + u16 total_num_ports; /* Total number of ports on the target device */ + /* DW10~ */ + struct enum_tlv_port_info port_info[]; +}; +#define ENUM_TOPO_QUERY_RSP_BASE_SIZE 40 /* exclusive port_info */ + +enum enum_tlv_type { /* M: Mandatory , O: optional */ + TLV_SLICE_INFO = 0, /* M */ + TLV_PORT_NUM = 1, /* M */ + TLV_PORT_INFO = 2, /* M */ + TLV_CAP_INFO = 4 /* M */ +}; + +struct enum_tlv_common { + u32 value : 16; + u32 len : 8; + u32 type : 8; +}; + +struct enum_tlv_slice_info { + u32 slice_id : 8; + u32 total_slice : 8; + u32 len : 8; + u32 type : 8; +}; +#define ENUM_TLV_SLICE_INFO_SZ 4 + +struct enum_tlv_port_num { + u32 total_num_ports : 16; + u32 len : 8; + u32 type : 8; + + u32 rsvd : 16; + u32 num_port_tlv : 16; +}; +#define ENUM_TLV_PORT_NUM_SZ 8 + +struct enum_tlv_cap_info { + u32 da : 1; + u32 rsvd0 : 7; + u32 mtu : 3; + u32 rsvd1 : 1; + u32 sup_mtu : 3; + u32 rsvd2 : 1; + u32 len : 8; + u32 type : 8; + + u32 class_code : 16; + u32 rsvd3 : 16; +}; +#define ENUM_TLV_CAP_INFO_SZ 8 + +void ub_enum_clear_ent_list(struct list_head *dev_list); +int ub_enum_entities_active(struct list_head *dev_list); +int ub_enum_topo_scan_ports(struct ub_entity *uent, u16 start_port, u16 num, + struct list_head *dev_list, void *buf); +struct ub_entity *ub_enum_get_port_r_uent(struct ub_port *port, void *buf); +size_t calc_enum_pld_header_size(struct enum_pld_scan_header *header, bool req); +int ub_cfg_read_guid(struct ub_entity *uent, u32 *dw); +int ub_enum_bfs_route_cal(struct list_head *uent_list); +int ub_query_port_na(struct ub_entity *uent, void *buf); +int ub_query_ent_na(struct ub_entity *uent, void *buf); +int ub_enum_probe(void); +void ub_enum_remove(void); +bool ub_type_valid(struct ub_entity *uent, bool is_ctl); +void ub_entity_type_init(struct ub_entity *uent); + +#endif /* __ENUM_H__ */ diff --git a/drivers/ub/ubus/eu.c b/drivers/ub/ubus/eu.c new file mode 100644 index 0000000000000000000000000000000000000000..5c99d44918e56f60fd38b7c4ff8c7a243cc930ea --- /dev/null +++ b/drivers/ub/ubus/eu.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus eu: " fmt + +#include + +#include "ubus.h" +#include "ubus_controller.h" +#include "eu.h" + +/* + * The ubus driver provides only the eid-upi table memory. + * The table configuration policy is developed by the vendor. + * Implemented in a unified manner in the future. + */ + +static int ub_eu_table_init_common(struct ub_entity *uent) +{ + struct ub_bus_controller *ubc = uent->ubc; + struct ub_eu_table *eu; + u32 entries, cfg_ret; + int ret; + + ret = ub_cfg_read_dword(uent, UB_EU_TEN, &entries); + if (ret) { + ub_err(uent, "EU TABLE get failed, ret=%d\n", ret); + return ret; + } + if (entries == 0) { + ub_err(uent, "EID-UPI Table Entry Num is 0\n"); + return -EINVAL; + } + + ub_info(uent, "EU TABLE entries num is %#x\n", entries); + + eu = kzalloc(sizeof(*eu), GFP_KERNEL); + if (!eu) + return -ENOMEM; + + eu->entries = entries; + eu->size = entries * EU_ENTRY_SIZE; + eu->addr = dma_alloc_coherent(&ubc->dev, eu->size, &eu->dma_addr, + GFP_KERNEL); + if (!eu->addr) { + ret = -ENOMEM; + goto free; + } + + cfg_ret = (u32)ub_cfg_write_dword(uent, UB_EU_TBA_L, + lower_32_bits(eu->dma_addr)); + cfg_ret |= (u32)ub_cfg_write_dword(uent, UB_EU_TBA_H, + upper_32_bits(eu->dma_addr)); + if (cfg_ret) { + ret = (int)cfg_ret; + ub_err(uent, "EU TABLE set failed, ret=%d\n", ret); + goto dma_free; + } + + memset(eu->addr, 0, eu->size); + uent->eu_table = eu; + + return 0; + +dma_free: + dma_free_coherent(&ubc->dev, eu->size, eu->addr, eu->dma_addr); +free: + kfree(eu); + return ret; +} + +static void ub_eu_table_uninit_common(struct ub_entity *uent) +{ + struct ub_eu_table *eu = uent->eu_table; + struct ub_bus_controller *ubc = uent->ubc; + u32 ret; + + uent->eu_table = NULL; + + ret = (u32)ub_cfg_write_dword(uent, UB_EU_TBA_L, 0); + ret |= (u32)ub_cfg_write_dword(uent, UB_EU_TBA_H, 0); + if (ret) + ub_err(uent, "EU TABLE unset failed, ret=%d\n", (int)ret); + + dma_free_coherent(&ubc->dev, eu->size, eu->addr, eu->dma_addr); + kfree(eu); +} + +void ub_eu_table_init(struct ub_entity *uent) +{ + struct ub_bus_controller *ubc = uent->ubc; + int ret; + + if (!is_ibus_controller(uent) || ubc->cluster) + return; + + ret = ub_eu_table_init_common(uent); + if (ret) + return; + + if (ubc->ops && ubc->ops->eu_table_init) { + ret = ubc->ops->eu_table_init(ubc); + if (ret) { + ub_err(uent, "EU TABLE private init failed, ret=%d\n", + ret); + goto uninit_common; + } + } + + ub_cfg_write_byte(uent, UB_TH_EN, 1); + + return; + +uninit_common: + ub_eu_table_uninit_common(uent); +} + +void ub_eu_table_uninit(struct ub_entity *uent) +{ + struct ub_bus_controller *ubc = uent->ubc; + + if (!uent->eu_table) + return; + + ub_cfg_write_byte(uent, UB_TH_EN, 0); + + if (ubc->ops && ubc->ops->eu_table_uninit) + ubc->ops->eu_table_uninit(ubc); + + ub_eu_table_uninit_common(uent); +} + +int ub_cfg_eu_table(struct ub_bus_controller *ubc, bool flag, u32 eid, u16 upi) +{ + struct ub_bus_controller_ops *ops = ubc->ops; + int ret; + + if (ubc->cluster) + return 0; + + if (!ops || !ops->eu_cfg) + return -ENODEV; + + ret = ops->eu_cfg(ubc, flag, eid, upi); + if (ret) + dev_err(&ubc->dev, "eu %s fail, eid[%#05x]<->upi[%#04x]\n", + flag ? "add" : "del", eid, upi); + + return ret; +} diff --git a/drivers/ub/ubus/eu.h b/drivers/ub/ubus/eu.h new file mode 100644 index 0000000000000000000000000000000000000000..cc527feaacd5d359098e0daddaa0e6b5fbc71808 --- /dev/null +++ b/drivers/ub/ubus/eu.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __EU_H__ +#define __EU_H__ + +struct ub_eu_table { /* eid-upi table */ + dma_addr_t dma_addr; + void *addr; + size_t size; + u32 entries; + void *private_data; /* For ubc vendor private */ +}; + +#pragma pack(2) +struct ub_eu_entry { + u32 eid[SZ_4]; + u16 upi; +}; +#pragma pack() + +#define EU_ENTRY_SIZE 18 + +void ub_eu_table_init(struct ub_entity *uent); +void ub_eu_table_uninit(struct ub_entity *uent); +int ub_cfg_eu_table(struct ub_bus_controller *ubc, bool flag, u32 eid, u16 upi); + +#endif /* __EU_H__ */ diff --git a/drivers/ub/ubus/instance.c b/drivers/ub/ubus/instance.c new file mode 100644 index 0000000000000000000000000000000000000000..2854765a5fae43f1e5045bc4d8a6d90b4ad371a3 --- /dev/null +++ b/drivers/ub/ubus/instance.c @@ -0,0 +1,970 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus instance: " fmt + +#include +#include + +#include "ubus.h" +#include "eid.h" +#include "resource.h" +#include "eu.h" +#include "ubus_driver.h" +#include "instance.h" + +#define DYNAMIC_DEFAULT_UBC 0 + +static LIST_HEAD(ubi_list); +static DEFINE_MUTEX(ubi_list_mutex); +static DEFINE_MUTEX(dynamic_mutex); +static u32 instance_count; +static u32 instance_start; + +typedef bool (*instance_match)(struct ub_bus_instance *bi, void *arg); + +static ssize_t instance_store(const struct bus_type *bus, const char *buf, + size_t count) +{ + unsigned long val; + ssize_t ret; + + ret = kstrtoul(buf, 0, &val); + if (ret < 0) + return ret; + + mutex_lock(&ubi_list_mutex); + if (val >= instance_count) { + pr_err("store instance %#lx over %#x\n", val, instance_count); + mutex_unlock(&ubi_list_mutex); + return -EINVAL; + } + + instance_start = val; + mutex_unlock(&ubi_list_mutex); + return count; +} + +static ssize_t instance_show(const struct bus_type *bus, char *buf) +{ + struct ub_bus_instance_info *info; + struct ub_bus_instance *bi; + u32 show, left, end, i = 0; + struct ub_guid *guid; + ssize_t len; + +#define MAX_DUMP 50 + mutex_lock(&ubi_list_mutex); + left = instance_count - instance_start; + show = left > MAX_DUMP ? MAX_DUMP : left; + end = instance_start + show; + len = sysfs_emit(buf, "count %#x, from %#x, show %#x\n", instance_count, + instance_start, show); + + list_for_each_entry(bi, &ubi_list, node) { + if (i < instance_start) { + i++; + continue; + } + + if (i == end) + break; + + info = &bi->info; + guid = &info->guid; + len += sysfs_emit_at(buf, len, "guid:"); + len += ub_show_guid(guid, buf + len); + len += sysfs_emit_at(buf, len, " type:%01x eid:%05x upi:%04x\n", + info->type, info->eid, info->upi); + i++; + } + + instance_start = 0; + mutex_unlock(&ubi_list_mutex); + return len; +} +BUS_ATTR_RW(instance); + +static void ub_unregister_bus_instance(struct ub_bus_instance *bi); +static void ub_bus_instance_destroy(struct ub_bus_instance *bi) +{ + guid_t *tar = (guid_t *)&guid_null; + + if (is_dynamic(bi)) + tar = &bi->info.guid.id; + + ummu_core_del_eid(tar, bi->info.eid, EID_NONE); + ub_unregister_bus_instance(bi); +} + +static void ub_release_bus_instance(struct kref *kref) +{ + struct ub_bus_instance *bi = + container_of(kref, struct ub_bus_instance, kref); + + if (bi->registered) + ub_bus_instance_destroy(bi); + + kfree(bi); +} + +static struct ub_bus_instance *ub_bus_instance_get(struct ub_bus_instance *bi) +{ + if (bi) + kref_get(&bi->kref); + + return bi; +} + +static void ub_bus_instance_put(struct ub_bus_instance *bi) +{ + if (bi) + kref_put(&bi->kref, ub_release_bus_instance); +} + +static bool eid_match(struct ub_bus_instance *bi, void *arg) +{ + if (bi == NULL || arg == NULL) + return false; + + u32 *eid = (u32 *)arg; + + return bi->info.eid == *eid; +} + +static struct ub_bus_instance *ub_alloc_bus_instance(void) +{ + struct ub_bus_instance *bi; + + bi = kzalloc(sizeof(*bi), GFP_KERNEL); + if (!bi) + return NULL; + + INIT_LIST_HEAD(&bi->node); + kref_init(&bi->kref); + INIT_LIST_HEAD(&bi->uents); + mutex_init(&bi->lock); + + return bi; +} + +static bool guid_match(struct ub_bus_instance *bi, void *arg) +{ + guid_t *guid = (guid_t *)arg; + + return guid_equal(&bi->info.guid.id, guid); +} + +static struct ub_bus_instance *ub_find_bus_instance(instance_match match, + void *arg) +{ + struct ub_bus_instance *bi; + + mutex_lock(&ubi_list_mutex); + list_for_each_entry(bi, &ubi_list, node) + if (match(bi, arg)) + goto out; + + bi = NULL; +out: + ub_bus_instance_get(bi); + mutex_unlock(&ubi_list_mutex); + + return bi; +} + +bool ub_bus_instance_exist(u32 eid) +{ + struct ub_bus_instance *bi; + + bi = ub_find_bus_instance(eid_match, &eid); + if (!bi) + return false; + + ub_bus_instance_put(bi); + return true; +} +EXPORT_SYMBOL_GPL(ub_bus_instance_exist); + +static int bi_duplicate_check(struct ub_bus_instance_info *n) +{ + struct ub_bus_instance_info *old; + struct ub_bus_instance *bi; + int ret; + + bi = ub_find_bus_instance(guid_match, &n->guid.id); + if (!bi) + return 0; + + old = &bi->info; + + if (n->type != old->type) { + pr_err("bus instance guid exist with different type\n"); + ub_bus_instance_put(bi); + return -EINVAL; + } + + if (n->eid) { + if (n->upi) + ret = (n->eid == old->eid && n->upi == old->upi) ? + -EEXIST : + -EINVAL; + else + ret = (n->eid == old->eid) ? -EEXIST : -EINVAL; + } else { + if (n->upi) + ret = (n->upi == old->upi) ? -EEXIST : -EINVAL; + else + ret = -EEXIST; + } + + if (ret == -EEXIST) + pr_warn("bus instance guid exist\n"); + else + pr_err("bus instance guid exist, but eid/upi invalid\n"); + + ub_bus_instance_put(bi); + + return ret; +} + +static int bi_valid_check(struct ub_bus_instance *bi) +{ + struct ub_bus_instance_info *info = &bi->info; + + if (info->guid.bits.type != UB_TYPE_BUS_INSTANCE || + guid_is_null((const guid_t *)&info->guid)) { + pr_err("guid type is not bus instance or guid_null\n"); + return -EINVAL; + } + + if (is_cluster(bi) && (info->eid == 0 || info->upi == 0 || + info->eid <= ubc_eid_end)) { + pr_err("cluster bi eid or upi invalid, eid=%#x, upi=%#x\n", + info->eid, info->upi); + return -EINVAL; + } + + if (is_server(bi) && info->eid && info->eid <= ubc_eid_end) { + pr_err("server bi eid within the local scope, eid=%#x\n", + info->eid); + return -EINVAL; + } + + return 0; +} + +static int ub_cfg_bus_instance_eid(struct ub_bus_instance *bi, bool alloc) +{ + struct ub_bus_instance_info *info = &bi->info; + struct ub_guid guid = info->guid; + u32 eid; + int ret; + + if (is_cluster(bi)) + return 0; + + if (alloc) { + if (info->eid) + return 0; + + ret = ub_eid_request(&guid.id, &eid); + if (ret) { + pr_err("bus instance eid cfg failed, ret=%d\n", ret); + return ret; + } + + info->eid = eid; + } else { + if (info->eid > ubc_eid_end) + return 0; + + ub_eid_release(info->eid); + info->eid = 0; + } + + return 0; +} + +static void ub_cfg_bus_instance_upi(struct ub_bus_instance *bi) +{ + struct ub_bus_instance_info *info = &bi->info; + + if (is_cluster(bi) || info->upi) + return; + + info->upi = UB_CP_UPI; +} + +static int ub_register_bus_instance(struct ub_bus_instance *bi) +{ + struct ub_bus_instance_info *info = &bi->info; + struct ub_bus_controller *ubc; + int ret, i = 0, count = 0; + + ret = bi_duplicate_check(info); + if (ret) + return ret; + + ret = bi_valid_check(bi); + if (ret) + return ret; + + ret = ub_cfg_bus_instance_eid(bi, true); + if (ret) + return ret; + + ub_cfg_bus_instance_upi(bi); + + if (is_static_server(bi)) { + ret = ub_cfg_eu_table(bi->major, true, bi->info.eid, + bi->info.upi); + if (ret) + goto out; + } else { + list_for_each_entry(ubc, &ubc_list, node) { + ret = ub_cfg_eu_table(ubc, true, bi->info.eid, + bi->info.upi); + if (ret) + goto cfg_fail; + count++; + } + } + + mutex_lock(&ubi_list_mutex); + bi->registered = true; + list_add_tail(&bi->node, &ubi_list); + instance_count++; + mutex_unlock(&ubi_list_mutex); + return 0; +cfg_fail: + list_for_each_entry(ubc, &ubc_list, node) { + if (i == count) + break; + + (void)ub_cfg_eu_table(ubc, false, bi->info.eid, bi->info.upi); + i++; + } +out: + /* upi no need unconfigure */ + (void)ub_cfg_bus_instance_eid(bi, false); + return ret; +} + +static void ub_unregister_bus_instance(struct ub_bus_instance *bi) +{ + struct ub_bus_controller *ubc; + + mutex_lock(&ubi_list_mutex); + if (!bi->registered) { + mutex_unlock(&ubi_list_mutex); + return; + } + + instance_count--; + list_del(&bi->node); + bi->registered = false; + mutex_unlock(&ubi_list_mutex); + + if (is_static_server(bi)) { + (void)ub_cfg_eu_table(bi->major, false, bi->info.eid, + bi->info.upi); + } else { + list_for_each_entry(ubc, &ubc_list, node) + (void)ub_cfg_eu_table(ubc, false, bi->info.eid, + bi->info.upi); + } + + /* upi no need unconfigure */ + (void)ub_cfg_bus_instance_eid(bi, false); +} + +int ub_static_bus_instance_init(struct ub_bus_controller *ubc) +{ + struct ub_bus_instance *bi; + struct ub_bus_controller *tmp; + int ret; + + if (ubc->ctl_no != 0) { + tmp = ub_find_bus_controller(0); + if (!tmp || !tmp->bi) + return -EINVAL; + + ubc->bi = ub_bus_instance_get(tmp->bi); + + return ub_cfg_eu_table(ubc, true, ubc->bi->info.eid, + ubc->bi->info.upi); + } + + bi = ub_alloc_bus_instance(); + if (!bi) + return -ENOMEM; + + bi->info.type = UBUS_INSTANCE_STATIC_SERVER; + guid_copy(&bi->info.guid.id, &ubc->uent->guid.id); + bi->info.guid.bits.type = UB_TYPE_BUS_INSTANCE; + bi->major = ubc; + + ret = ub_register_bus_instance(bi); + if (ret) { + dev_err(&ubc->dev, "static server register bi failed ret=%d\n", + ret); + goto put; + } + + ret = ummu_core_add_eid((guid_t *)&guid_null, bi->info.eid, + EID_NONE); + if (ret) { + dev_err(&ubc->dev, "static server ummu core add eid, ret=%d\n", + ret); + goto unregister; + } + + ubc->bi = ub_bus_instance_get(bi); + + ub_bus_instance_put(bi); /* put the init one */ + + return 0; +unregister: + ub_unregister_bus_instance(bi); +put: + ub_bus_instance_put(bi); + return ret; +} + +void ub_static_bus_instance_uninit(struct ub_bus_controller *ubc) +{ + if (ubc->ctl_no != 0) + (void)ub_cfg_eu_table(ubc, false, ubc->bi->info.eid, + ubc->bi->info.upi); + + ub_bus_instance_put(ubc->bi); + ubc->bi = NULL; +} + +static struct ub_bus_instance * +ub_dynamic_bus_instance_create(struct ub_bus_instance_info *info, enum eid_type type) +{ + struct ub_bus_instance *bi; + struct ub_bus_controller *ubc; + int ret; + + ubc = ub_find_bus_controller(DYNAMIC_DEFAULT_UBC); + if (!ubc) { + pr_err("ubc 0 not exist\n"); + return (struct ub_bus_instance *)ERR_PTR(-ENODEV); + } + + bi = ub_alloc_bus_instance(); + if (!bi) + return (struct ub_bus_instance *)ERR_PTR(-ENOMEM); + + if (ubc->cluster) + info->type = UBUS_INSTANCE_DYNAMIC_CLUSTER; + else + info->type = UBUS_INSTANCE_DYNAMIC_SERVER; + + bi->info = *info; + bi->major = ubc; + + ret = ub_register_bus_instance(bi); + if (ret) + goto put; + + ret = ummu_core_add_eid(&bi->info.guid.id, bi->info.eid, type); + if (ret) { + pr_err("bus instance add eid, ret=%d\n", ret); + goto unregister; + } + + return bi; + +unregister: + ub_unregister_bus_instance(bi); +put: + ub_bus_instance_put(bi); + return (struct ub_bus_instance *)ERR_PTR(ret); +} + +static void bi_info_init(struct ub_bus_instance_info *info, + const struct ubus_cmd_bi_create *create) +{ + info->type = create->type; + info->upi = create->upi; + info->eid = create->eid; + guid_copy(&info->guid.id, (const guid_t *)create->guid); +} + +int ub_ioctl_bus_instance_create(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_create); + struct ubus_cmd_bi_create create = {}; + struct ub_bus_instance_info info = {}; + struct ub_bus_instance *bi; + + if (copy_from_user(&create, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + bi_info_init(&info, &create); + + mutex_lock(&dynamic_mutex); + bi = ub_dynamic_bus_instance_create(&info, EID_NONE); + if (IS_ERR(bi)) { + pr_err("bus instance create failed, ret=%ld\n", PTR_ERR(bi)); + mutex_unlock(&dynamic_mutex); + return PTR_ERR(bi); + } + + create.eid = bi->info.eid; + create.upi = bi->info.upi; + + if (copy_to_user(uptr + UBUS_IOCTL_HEADER_SIZE, &create, size)) { + ub_bus_instance_put(bi); + pr_err("bus instance copy to user failed\n"); + mutex_unlock(&dynamic_mutex); + return -EFAULT; + } + + mutex_unlock(&dynamic_mutex); + return 0; +} + +int ub_ioctl_bus_instance_destroy(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_destroy); + struct ubus_cmd_bi_destroy destroy = {}; + struct ub_bus_instance *bi; + char b_str[SZ_64]; + + if (copy_from_user(&destroy, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + (void)snprintf(b_str, SZ_64, "%#llx %llx", + *((u64 *)&destroy.guid[SZ_8]), + *((u64 *)&destroy.guid[0])); + + mutex_lock(&dynamic_mutex); + bi = ub_find_bus_instance(guid_match, destroy.guid); + if (!bi) { + pr_err("bus instance destroy invalid, guid=%s\n", b_str); + mutex_unlock(&dynamic_mutex); + return -ENODEV; + } + + if (!is_dynamic(bi)) { + pr_err("instance %s is not dynamic\n", b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EINVAL; + } + + if (kref_read(&bi->kref) != 2) { /* 2 is original + find */ + pr_err("instance %s is still in use\n", b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EBUSY; + } + + ub_bus_instance_put(bi); /* put find */ + ub_bus_instance_put(bi); /* real put */ + + mutex_unlock(&dynamic_mutex); + return 0; +} + +int ub_msg_bus_instance_create(struct ub_bus_controller *ubc, u32 *guid, u32 eid, + u16 upi, enum eid_type type) +{ + struct ub_bus_instance_info info = {}; + struct ub_bus_instance *bi; + int ret = 0; + + info.upi = upi; + info.eid = eid; + guid_copy(&info.guid.id, (const guid_t *)guid); + + mutex_lock(&dynamic_mutex); + bi = ub_dynamic_bus_instance_create(&info, type); + if (IS_ERR(bi)) { + ret = PTR_ERR(bi); + dev_err(&ubc->dev, "msg bus instance create failed, ret=%d\n", + ret); + } + + mutex_unlock(&dynamic_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(ub_msg_bus_instance_create); + +int ub_msg_bus_instance_destroy(struct ub_bus_controller *ubc, u32 *guid) +{ + struct ub_bus_instance *bi; + char b_str[SZ_64]; + + (void)snprintf(b_str, SZ_64, "%#llx %llx", *((u64 *)&guid[SZ_2]), + *((u64 *)&guid[0])); + + mutex_lock(&dynamic_mutex); + bi = ub_find_bus_instance(guid_match, guid); + if (!bi) { + dev_err(&ubc->dev, + "msg bus instance destroy invalid, guid=%s\n", b_str); + mutex_unlock(&dynamic_mutex); + return -ENODEV; + } + + if (!is_dynamic_cluster(bi)) { + dev_err(&ubc->dev, "msg destroy instance %s type invalid\n", + b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EINVAL; + } + + if (kref_read(&bi->kref) != 2) { /* 2 is original + find */ + dev_err(&ubc->dev, "msg instance %s still in use\n", b_str); + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + return -EBUSY; + } + + ub_bus_instance_put(bi); /* put find */ + ub_bus_instance_put(bi); /* real put */ + + mutex_unlock(&dynamic_mutex); + return 0; +} + +void ub_dynamic_bus_instance_drain(void) +{ + struct ub_bus_instance *bi, *tmp; + + mutex_lock(&dynamic_mutex); + + list_for_each_entry_safe(bi, tmp, &ubi_list, node) + if (is_dynamic(bi)) + ub_bus_instance_put(bi); + + mutex_unlock(&dynamic_mutex); +} + +static int +ub_static_cluster_instance_create(struct ub_bus_controller *ubc, u32 *guid, + u32 eid, u16 upi) +{ + struct ub_bus_instance *bi; + int ret; + + bi = ub_alloc_bus_instance(); + if (!bi) + return -ENOMEM; + + bi->info.type = UBUS_INSTANCE_STATIC_CLUSTER; + guid_copy(&bi->info.guid.id, (guid_t *)guid); + bi->info.eid = eid; + bi->info.upi = upi; + bi->major = ubc; + + ret = ub_register_bus_instance(bi); + if (ret) + goto put; + + ret = ummu_core_add_eid((guid_t *)&guid_null, bi->info.eid, + EID_NONE); + if (ret) { + dev_err(&ubc->dev, "bus instance add eid failed, ret=%d\n", ret); + goto unregister; + } + + ubc->cluster_bi = bi; + return 0; + +unregister: + ub_unregister_bus_instance(bi); +put: + ub_bus_instance_put(bi); + return ret; +} + +int ub_notify_bus_instance_handle(struct ub_bus_controller *ubc, bool flag, + u32 *guid, u32 eid, u16 upi) +{ + struct ub_bus_instance *bi; + struct ub_guid *tmp; + int ret; + + if (!flag) { + bi = ub_find_bus_instance(guid_match, guid); + if (!bi) { + dev_err(&ubc->dev, "notify can't find bus instance\n"); + return -ENODEV; + } + + if (!is_static_cluster(bi) || bi->info.eid != eid || + bi->info.upi != upi) { + dev_err(&ubc->dev, "notify bus instance is invalid\n"); + ub_bus_instance_put(bi); + return -EINVAL; + } + + /* May receive the same UBC Notify message */ + if (ubc->cluster_bi) + ub_bus_instance_put(bi); + else + ubc->cluster_bi = bi; + + return 0; + } + + tmp = (struct ub_guid *)guid; + if (tmp->bits.type != UB_TYPE_BUS_INSTANCE) { + dev_err(&ubc->dev, "notify msg guid type is invalid\n"); + return -EINVAL; + } + + /* BI may have already been created in other UBC Notify messages */ + ret = ub_static_cluster_instance_create(ubc, guid, eid, upi); + if (ret && ret != -EEXIST) { + dev_err(&ubc->dev, "create static cluster instance failed\n"); + return ret; + } + + if (!ubc->cluster_bi) + ubc->cluster_bi = ub_find_bus_instance(guid_match, guid); + + return 0; +} + +void ub_static_cluster_instance_drain(void) +{ + struct ub_bus_controller *ubc; + + list_for_each_entry(ubc, &ubc_list, node) { + ub_bus_instance_put(ubc->cluster_bi); + ubc->cluster_bi = NULL; + } +} + +/* Only for dynamic use */ +static int get_bus_instance_and_uent(u8 *b_guid, u8 *d_guid, + struct ub_bus_instance **p_bi, + struct ub_entity **p_uent) +{ + char b_str[SZ_64], d_str[SZ_64]; + struct ub_bus_instance *bi; + struct ub_entity *uent; + int ret = -ENODEV; + + (void)snprintf(b_str, SZ_64, "%#llx %llx", *((u64 *)&b_guid[SZ_8]), + *((u64 *)&b_guid[0])); + (void)snprintf(d_str, SZ_64, "%#llx %llx", *((u64 *)&d_guid[SZ_8]), + *((u64 *)&d_guid[0])); + + bi = ub_find_bus_instance(guid_match, b_guid); + if (!bi) + goto err; + + if (!is_dynamic(bi)) { + ret = -EINVAL; + goto put; + } + + uent = ub_get_ent_by_guid((struct ub_guid *)d_guid); + if (!uent) + goto put; + + if (is_p_device(uent)) { + pr_err("fad dev not support bind again\n"); + ub_entity_put(uent); + goto put; + } + + *p_bi = bi; + *p_uent = uent; + return 0; +put: + ub_bus_instance_put(bi); +err: + pr_err("get bi and uent failed, bi_guid=%s, dev_guid=%s\n", b_str, d_str); + return ret; +} + +static int ub_bind_bus_instance(struct ub_entity *uent, struct ub_bus_instance *bi) +{ + if (!uent || uent->bi || !bi || !bi->registered) + return -EINVAL; + + uent->bi = ub_bus_instance_get(bi); + + mutex_lock(&bi->lock); + list_add_tail(&uent->instance_node, &bi->uents); + mutex_unlock(&bi->lock); + + ub_entity_decoder_map_mmio(uent); + return 0; +} + +static void ub_unbind_bus_instance(struct ub_entity *uent) +{ + if (!uent || !uent->bi) + return; + + mutex_lock(&uent->bi->lock); + list_del(&uent->instance_node); + mutex_unlock(&uent->bi->lock); + + ub_bus_instance_put(uent->bi); + uent->bi = NULL; + ub_entity_decoder_unmap_mmio(uent); +} + +int ub_ioctl_bus_instance_bind(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_bind); + struct ubus_cmd_bi_bind bind = {}; + struct ub_bus_instance *bi, *old; + struct ub_entity *uent; + char b_str[SZ_64]; + int ret; + + if (copy_from_user(&bind, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + mutex_lock(&dynamic_mutex); + ret = get_bus_instance_and_uent(bind.instance_guid, bind.dev_guid, &bi, + &uent); + if (ret) { + mutex_unlock(&dynamic_mutex); + return ret; + } + + (void)snprintf(b_str, SZ_64, "%#llx %llx", + *((u64 *)&bind.instance_guid[SZ_8]), + *((u64 *)&bind.instance_guid[0])); + + mutex_lock(&uent->instance_lock); + old = uent->bi; + if (!old) { + ub_err(uent, "dev has no static instance\n"); + ret = -EINVAL; + goto out; + } + + if (is_static(old)) { + ub_unbind_bus_instance(uent); + ret = ub_bind_bus_instance(uent, bi); + if (ret) { + ub_err(uent, "bind instance failed, guid=%s\n", b_str); + (void)ub_bind_bus_instance(uent, uent->ubc->bi); + } + } else { + /* If bind the same again, do nothing */ + if (old != bi) { + ub_err(uent, "dev already bind instance\n"); + ret = -EBUSY; + } + } + +out: + ub_entity_put(uent); + ub_bus_instance_put(bi); + mutex_unlock(&uent->instance_lock); + mutex_unlock(&dynamic_mutex); + return ret; +} + +int ub_ioctl_bus_instance_unbind(void __user *uptr) +{ + size_t size = sizeof(struct ubus_cmd_bi_unbind); + struct ubus_cmd_bi_unbind unbind = {}; + struct ub_bus_instance *bi; + struct ub_entity *uent; + char b_str[SZ_64]; + int ret; + + if (copy_from_user(&unbind, uptr + UBUS_IOCTL_HEADER_SIZE, size)) + return -EFAULT; + + mutex_lock(&dynamic_mutex); + ret = get_bus_instance_and_uent(unbind.instance_guid, unbind.dev_guid, + &bi, &uent); + if (ret) { + mutex_unlock(&dynamic_mutex); + return ret; + } + + (void)snprintf(b_str, SZ_64, "%#llx %llx", + *((u64 *)&unbind.instance_guid[SZ_8]), + *((u64 *)&unbind.instance_guid[0])); + + mutex_lock(&uent->instance_lock); + if (uent->bi != bi) { + ub_err(uent, "dev not bind bus instance, guid=%s\n", b_str); + ret = -EINVAL; + goto out; + } + + ub_unbind_bus_instance(uent); + ret = ub_bind_bus_instance(uent, uent->ubc->bi); + if (ret) { + ub_err(uent, "unbind instance failed, guid=%s\n", b_str); + (void)ub_bind_bus_instance(uent, bi); + } + +out: + ub_entity_put(uent); + ub_bus_instance_put(bi); + mutex_unlock(&uent->instance_lock); + mutex_unlock(&dynamic_mutex); + return ret; +} + +int ub_default_bus_instance_init(struct ub_entity *uent) +{ + struct ub_bus_instance *bi; + bool use_cluster; + int ret; + + if (is_switch(uent)) + return 0; + + use_cluster = is_p_device(uent) || is_p_idevice(uent) || + (is_ibus_controller(uent) && uent->ubc->cluster); + + if (use_cluster) { + mutex_lock(&dynamic_mutex); + bi = ub_find_bus_instance(eid_match, &uent->user_eid); + } else { + bi = uent->ubc->bi; + } + + if (!bi) { + if (use_cluster) + mutex_unlock(&dynamic_mutex); + ub_err(uent, "get default bi NULL\n"); + return -EINVAL; + } + + mutex_lock(&uent->instance_lock); + ret = ub_bind_bus_instance(uent, bi); + mutex_unlock(&uent->instance_lock); + + if (use_cluster) { + ub_bus_instance_put(bi); + mutex_unlock(&dynamic_mutex); + } + + return ret; +} + +void ub_default_bus_instance_uninit(struct ub_entity *uent) +{ + if (is_switch(uent)) + return; + + mutex_lock(&uent->instance_lock); + ub_unbind_bus_instance(uent); + mutex_unlock(&uent->instance_lock); +} diff --git a/drivers/ub/ubus/instance.h b/drivers/ub/ubus/instance.h new file mode 100644 index 0000000000000000000000000000000000000000..02dcae1ab33b5549d42da4bd6320cb80c085a093 --- /dev/null +++ b/drivers/ub/ubus/instance.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __INSTANCE_H__ +#define __INSTANCE_H__ + +#include + +extern struct bus_attribute bus_attr_instance; + +#define UBUS_INSTANCE_STATIC_SERVER 0 +#define UBUS_INSTANCE_STATIC_CLUSTER 1 + +#define is_static_server(bi) ((bi)->info.type == UBUS_INSTANCE_STATIC_SERVER) +#define is_static_cluster(bi) ((bi)->info.type == UBUS_INSTANCE_STATIC_CLUSTER) +#define is_dynamic_server(bi) ((bi)->info.type == UBUS_INSTANCE_DYNAMIC_SERVER) +#define is_dynamic_cluster(bi) ((bi)->info.type == UBUS_INSTANCE_DYNAMIC_CLUSTER) +#define is_static(bi) (is_static_server(bi) || is_static_cluster(bi)) +#define is_dynamic(bi) (is_dynamic_server(bi) || is_dynamic_cluster(bi)) +#define is_server(bi) (is_static_server(bi) || is_dynamic_server(bi)) +#define is_cluster(bi) (is_static_cluster(bi) || is_dynamic_cluster(bi)) + +int ub_static_bus_instance_init(struct ub_bus_controller *ubc); +void ub_static_bus_instance_uninit(struct ub_bus_controller *ubc); +int ub_ioctl_bus_instance_create(void __user *uptr); +int ub_ioctl_bus_instance_destroy(void __user *uptr); +int ub_ioctl_bus_instance_bind(void __user *uptr); +int ub_ioctl_bus_instance_unbind(void __user *uptr); +int ub_msg_bus_instance_create(struct ub_bus_controller *ubc, u32 *guid, u32 eid, + u16 upi, enum eid_type type); +int ub_msg_bus_instance_destroy(struct ub_bus_controller *ubc, u32 *guid); +void ub_dynamic_bus_instance_drain(void); +int ub_notify_bus_instance_handle(struct ub_bus_controller *ubc, bool flag, + u32 *guid, u32 eid, u16 upi); +void ub_static_cluster_instance_drain(void); +int ub_default_bus_instance_init(struct ub_entity *uent); +void ub_default_bus_instance_uninit(struct ub_entity *uent); +bool ub_bus_instance_exist(u32 eid); + +#endif /* __INSTANCE_H__ */ diff --git a/drivers/ub/ubus/interrupt.c b/drivers/ub/ubus/interrupt.c new file mode 100644 index 0000000000000000000000000000000000000000..d1ca667250fe50a47fa0972387d3ec146090cd7b --- /dev/null +++ b/drivers/ub/ubus/interrupt.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus interrupt: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ubus.h" + +enum ub_intr_tbl_type { + UB_INTR_VECTOR_TBL, + UB_INTR_ADDR_TBL +}; + +struct ub_intr_vector_tbl { + unsigned int vector; + unsigned int addr_index : 16; + unsigned int mask : 1; + unsigned int reserved : 15; +}; + +struct ub_intr_addr_tbl { + unsigned int intr_addr_l; + unsigned int intr_addr_h; + unsigned int token_id : 20; + unsigned int valid : 1; + unsigned int reserved0 : 11; + unsigned int dst_eid_0; + unsigned int dst_eid_1; + unsigned int dst_eid_2; + unsigned int dst_eid_3; +}; + +void ub_intr_init(struct ub_entity *uent) +{ + if (uent->cfg1_bitmap[0] & UB_INT_TYPE2_CAP_BIT) + uent->intr_type1 = 0; + else if (uent->cfg1_bitmap[0] & UB_INT_TYPE1_CAP_BIT) + uent->intr_type1 = 1; + else + uent->no_intr = 1; +} + +u32 ub_int_type1_vec_count(struct ub_entity *uent) +{ + u16 vector_num = 0; + int ret; + + ret = ub_cfg_read_word(uent, UB_INT_TYPE1_SUP_INT_NUM, &vector_num); + if (ret) + return 0; + + /* Mapping between the number of bitmaps and vectors defined in the protocol */ + vector_num = 1 << vector_num; + + return vector_num; +} +EXPORT_SYMBOL_GPL(ub_int_type1_vec_count); + +static void __iomem *ub_intr_map_region(struct ub_entity *uent, + enum ub_intr_tbl_type type, int nr_entries) +{ + u32 addr_reg_l, addr_reg_h, offset_l, offset_h; + resource_size_t tb_phys_addr; + unsigned long flags, sz; + u64 offset; + int ret; + + if (nr_entries <= 0) + return NULL; + if (type == UB_INTR_VECTOR_TBL) { + addr_reg_l = UB_INT_VECTOR_TBL_SA_L; + addr_reg_h = UB_INT_VECTOR_TBL_SA_H; + sz = (unsigned long)(nr_entries * UB_INTR_VECTOR_ENTRY_SIZE); + } else { + addr_reg_l = UB_INT_ADDR_TBL_SA_L; + addr_reg_h = UB_INT_ADDR_TBL_SA_H; + sz = (unsigned long)(nr_entries * UB_INTR_ADDR_ENTRY_SIZE); + } + + ret = ub_cfg_read_dword(uent, addr_reg_l, &offset_l); + ret |= ub_cfg_read_dword(uent, addr_reg_h, &offset_h); + if (ret) { + ub_err(uent, "read addr register failed, ret=%d\n", ret); + return NULL; + } + + offset = ((u64)offset_h << 32) | offset_l; + + flags = ub_resource_flags(uent, 0); + if (!flags || (flags & IORESOURCE_UNSET)) + return NULL; + + if (offset + sz > ub_resource_len(uent, 0)) { + ub_err(uent, "UB interrupt table(type:%u) off:%#llx + sz:%#lx is out of range!\n", + type, offset, sz); + return NULL; + } + tb_phys_addr = ub_resource_start(uent, 0) + offset; + + return ioremap(tb_phys_addr, sz); +} + +static int int_type1_capability_init(struct ub_entity *uent, + struct ub_usi_entry *entries, int nvec, + struct irq_affinity *affd) +{ + int ret; + + uent->intr_enabled = 1; + + ret = usi_setup_interrupts(uent, entries, nvec, affd); + if (ret) + goto out_disable; + + ub_cfg_write_byte(uent, UB_INT_TYPE1_ENABLE, 1); + return 0; + +out_disable: + uent->intr_enabled = 0; + + return ret; +} + +static int int_type2_capability_init(struct ub_entity *uent, + struct ub_usi_entry *entries, int nvec, + struct irq_affinity *affd) +{ + void __iomem *vector_base, *addr_base; + int vector_num, addr_num; + int ret; + + uent->intr_enabled = 1; + + vector_num = (int)ub_intr_vec_count(uent); + /* Request & Map UB interrupt table region */ + vector_base = ub_intr_map_region(uent, UB_INTR_VECTOR_TBL, vector_num); + if (!vector_base) { + ret = -ENOMEM; + goto out_disable; + } + addr_num = (int)ub_intr_addr_count(uent); + addr_base = ub_intr_map_region(uent, UB_INTR_ADDR_TBL, addr_num); + if (!addr_base) { + iounmap(vector_base); + ret = -ENOMEM; + goto out_disable; + } + uent->intr_vector_base = vector_base; + uent->intr_addr_base = addr_base; + + ret = usi_setup_interrupts(uent, entries, nvec, affd); + if (ret) + goto out_disable; + + ub_cfg_write_byte(uent, UB_INT_MASK, 0); + ub_cfg_write_byte(uent, UB_INT_EN, 1); + + return 0; + +out_disable: + uent->intr_enabled = 0; + + return ret; +} + +static int ub_msi_supported(struct ub_entity *uent, int nvec) +{ + if (!uent || uent->no_intr || nvec < 1) + return 0; + + return 1; +} + +static int ub_usi_entry_invalid_check(struct ub_usi_entry *ents, + int nvec, int nr_ents) +{ + int i, j; + + if (!ents) + return 0; + + for (i = 0; i < nvec; i++) { + if (ents[i].entry >= nr_ents) + return -EINVAL; + + for (j = i + 1; j < nvec; j++) + if (ents[i].entry == ents[j].entry) + return -EINVAL; + } + + return 0; +} + +static int __ub_enable_usi_range(struct ub_entity *uent, + struct ub_usi_entry *entries, int minvec, + int maxvec, struct irq_affinity *affd) +{ + int hwsize, nvec = maxvec; + int ret; + + if (maxvec < minvec) + return -ERANGE; + + if (!ub_msi_supported(uent, nvec)) + return -EINVAL; + + if (uent->intr_type1) + hwsize = (int)ub_int_type1_vec_count(uent); + else + hwsize = (int)ub_intr_vec_count(uent); + if (hwsize == 0) { + ub_err(uent, "int vector cnt is zero.\n"); + return -ENOSPC; + } + + ret = ub_usi_entry_invalid_check(entries, nvec, hwsize); + if (ret) + return ret; + + if (hwsize < nvec) + nvec = hwsize; + if (nvec < minvec) + return -ENOSPC; + + ret = ub_setup_msi_context(uent); + if (ret) + return ret; + + if (!ub_setup_usi_device_domain(uent, hwsize)) { + ub_err(uent, "ub setup device domain failed.\n"); + return -ENODEV; + } + + if (affd) { + nvec = ub_irq_calc_affinity_vectors(minvec, nvec, affd); + if (nvec < minvec) { + ub_err(uent, "irq calc affd failed.\n"); + return -ENOSPC; + } + } + + if (uent->intr_type1) + ret = int_type1_capability_init(uent, entries, nvec, affd); + else + ret = int_type2_capability_init(uent, entries, nvec, affd); + if (ret) + return ret; + + return nvec; +} + +int ub_alloc_irq_vectors_affinity(struct ub_entity *uent, unsigned int min_vecs, + unsigned int max_vecs, unsigned int flags, + struct irq_affinity *affd) +{ + struct irq_affinity msi_default_affd = {}; + + if (flags & UB_IRQ_AFFINITY) { + if (!affd) + affd = &msi_default_affd; + } else { + if (WARN_ON(affd)) + affd = NULL; + } + + return __ub_enable_usi_range(uent, NULL, min_vecs, max_vecs, affd); +} +EXPORT_SYMBOL_GPL(ub_alloc_irq_vectors_affinity); + +int ub_irq_vector(struct ub_entity *uent, unsigned int nr) +{ + unsigned int irq; + + if (!uent) + return -EINVAL; + + if (uent->intr_enabled) { + irq = msi_get_virq(&uent->dev, nr); + return irq ? (int)irq : -EINVAL; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ub_irq_vector); + +const struct cpumask *ub_irq_get_affinity(struct ub_entity *dev, int nr) +{ + int idx, irq = ub_irq_vector(dev, nr); + struct msi_desc *desc; + + if (WARN_ON_ONCE(irq <= 0)) + return NULL; + + desc = irq_get_msi_desc(irq); + /* Interrupts can be allocated without affinity descriptor */ + if (!desc->affinity) + return NULL; + + /* + * INT_TYPE1 has a mask array in the descriptor. + * INT_TYPE2 has a single mask. + */ + idx = dev->intr_type1 ? nr : 0; + return &desc->affinity[idx].mask; +} +EXPORT_SYMBOL_GPL(ub_irq_get_affinity); diff --git a/drivers/ub/ubus/interrupt.h b/drivers/ub/ubus/interrupt.h new file mode 100644 index 0000000000000000000000000000000000000000..9e7a5173793afce9532acf6080cecf9390c4c2aa --- /dev/null +++ b/drivers/ub/ubus/interrupt.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __INTERRUPT_H__ +#define __INTERRUPT_H__ + +void ub_intr_init(struct ub_entity *uent); + +#endif /* __INTERRUPT_H__ */ diff --git a/drivers/ub/ubus/ioctl.c b/drivers/ub/ubus/ioctl.c new file mode 100644 index 0000000000000000000000000000000000000000..0825a87d8b81ffec8f1069aee176208efcbff5e4 --- /dev/null +++ b/drivers/ub/ubus/ioctl.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus ioctl: " fmt + +#include +#include + +#include "ubus.h" +#include "instance.h" +#include "ioctl.h" + +#define UBUS_MAX_DEVICES 1 +#define UBUS_DEVICE_NAME "unified_bus" +#define UBUS_CLASS_NAME "unified_bus" + +struct unified_bus_ctx { + dev_t devno; + struct cdev cdev; + struct class *ub_class; + struct device *dev; +}; +static struct unified_bus_ctx ubus_ctx; + +static int ubus_fops_open(struct inode *inode, struct file *filep) +{ + return 0; +} + +static int ubus_fops_release(struct inode *inode, struct file *filep) +{ + return 0; +} + +static int ub_ioctl_bus_instance(void __user *uptr) +{ + struct ubus_ioctl_bus_instance bi; + + if (copy_from_user(&bi, uptr, UBUS_IOCTL_HEADER_SIZE)) + return -EFAULT; + + pr_info("ub ioctl bus instance, sub_cmd=%#x\n", bi.sub_cmd); + switch (bi.sub_cmd) { + case UBUS_CMD_BI_CREATE: + if (bi.argsz != sizeof(struct ubus_cmd_bi_create)) + return -EINVAL; + return ub_ioctl_bus_instance_create(uptr); + case UBUS_CMD_BI_DESTROY: + if (bi.argsz != sizeof(struct ubus_cmd_bi_destroy)) + return -EINVAL; + return ub_ioctl_bus_instance_destroy(uptr); + case UBUS_CMD_BI_BIND: + if (bi.argsz != sizeof(struct ubus_cmd_bi_bind)) + return -EINVAL; + return ub_ioctl_bus_instance_bind(uptr); + case UBUS_CMD_BI_UNBIND: + if (bi.argsz != sizeof(struct ubus_cmd_bi_unbind)) + return -EINVAL; + return ub_ioctl_bus_instance_unbind(uptr); + default: + pr_err("ubus bi sub cmd not support, cmd=%#x\n", bi.sub_cmd); + return -EINVAL; + } +} + +static long ubus_fops_unl_ioctl(struct file *filep, + unsigned int cmd, unsigned long arg) +{ + void __user *uptr = (void __user *)arg; + + switch (cmd) { + case UBUS_IOCTL_GET_API_VERSION: + return UBUS_API_VERSION; + case UBUS_IOCTL_BUS_INSTANCE: + return ub_ioctl_bus_instance(uptr); + default: + pr_err("ubus ioctl cmd %#x not support\n", cmd); + return -ENOTTY; + } +} + +static const struct file_operations ubus_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ubus_fops_unl_ioctl, + .open = ubus_fops_open, + .release = ubus_fops_release, +}; + +int ub_cdev_init(void) +{ + struct cdev *cdev = &ubus_ctx.cdev; + struct device *dev; + struct class *cls; + dev_t devno; + int ret; + + ret = alloc_chrdev_region(&devno, 0, UBUS_MAX_DEVICES, UBUS_DEVICE_NAME); + if (ret) + return ret; + + cdev_init(cdev, &ubus_fops); + ret = cdev_add(cdev, devno, UBUS_MAX_DEVICES); + if (ret) + goto out_unregister; + + cls = class_create(UBUS_CLASS_NAME); + if (IS_ERR(cls)) { + ret = PTR_ERR(cls); + goto out_del; + } + + dev = device_create(cls, NULL, devno, NULL, UBUS_DEVICE_NAME); + if (IS_ERR(dev)) { + ret = PTR_ERR(dev); + goto out_destroy; + } + + ubus_ctx.ub_class = cls; + ubus_ctx.dev = dev; + ubus_ctx.devno = devno; + return 0; + +out_destroy: + class_destroy(cls); +out_del: + cdev_del(cdev); +out_unregister: + unregister_chrdev_region(devno, UBUS_MAX_DEVICES); + return ret; +} + +void ub_cdev_uninit(void) +{ + device_destroy(ubus_ctx.ub_class, ubus_ctx.devno); + class_destroy(ubus_ctx.ub_class); + cdev_del(&ubus_ctx.cdev); + unregister_chrdev_region(ubus_ctx.devno, UBUS_MAX_DEVICES); +} diff --git a/drivers/ub/ubus/ioctl.h b/drivers/ub/ubus/ioctl.h new file mode 100644 index 0000000000000000000000000000000000000000..139a5bfc0112850c9ee93891dcd64eed17d36562 --- /dev/null +++ b/drivers/ub/ubus/ioctl.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __IOCTL_H__ +#define __IOCTL_H__ + +int ub_cdev_init(void); +void ub_cdev_uninit(void); + +#endif /* __IOCTL_H__ */ diff --git a/drivers/ub/ubus/link.c b/drivers/ub/ubus/link.c new file mode 100644 index 0000000000000000000000000000000000000000..9d8d579d8c01ad6ce04c1add13df5e5ad9476ba5 --- /dev/null +++ b/drivers/ub/ubus/link.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus link: " fmt + +#include + +#include "ubus.h" +#include "msg.h" +#include "enum.h" +#include "port.h" +#include "route.h" +#include "ubus_entity.h" +#include "ubus_driver.h" +#include "services/hotplug/hotplug.h" +#include "link.h" + +static struct ub_entity *ublc_get_port_r_uent(struct ub_port *port) +{ + struct ub_entity *r_uent = NULL; + void *buf; + +#define UB_TOPO_BUF_SZ SZ_4K + buf = kzalloc(UB_TOPO_BUF_SZ, GFP_KERNEL); + if (!buf) + return NULL; + + r_uent = ub_enum_get_port_r_uent(port, buf); + if (!r_uent) { + port->r_index = 0; + guid_copy(&port->r_guid, &guid_null); + } + + kfree(buf); + return r_uent; +} + +static void ublc_stop_devices(struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry_reverse(uent, dev_list, node) + ub_stop_ent(uent); +} + +static void ublc_remove_devices(struct list_head *dev_list) +{ + struct ub_entity *uent, *tmp; + + list_for_each_entry_safe_reverse(uent, tmp, dev_list, node) + ub_remove_ent(uent); +} + +static void ublc_update_route_link_down(struct ub_port *port) +{ + struct ub_entity *uent = port->uent; + + ub_route_clear_port(port); + + if (ub_entity_support_forward(uent)) + ub_route_del_bfs(uent); + + ub_route_sync_all(); +} + +/** + * ublc_enum_at_port() - enum at port to find new devices + * @port: the port that has new device plugged in or all port down remove + * @dev_list: a list to store the new found devices + * + * this func use bfs to enum devices and put them into dev_list, + * which means the previous device in dev_list is enumerated previous + */ +static int ublc_enum_at_port(struct ub_port *port, struct list_head *dev_list) +{ + void *buf; + int ret; + +#define UB_TOPO_BUF_SZ SZ_4K + buf = kzalloc(UB_TOPO_BUF_SZ, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = ub_enum_topo_scan_ports(port->uent, port->index, 1, dev_list, buf); + if (ret) { + port->r_index = 0; + guid_copy(&port->r_guid, &guid_null); + } + + kfree(buf); + return ret; +} + +static int ublc_update_route_link_up(struct ub_port *port) +{ + struct ub_entity *uent = port->uent, *r_uent = port->r_uent; + struct ub_port *r_port; + int ret; + + r_port = port->r_uent->ports + port->r_index; + ret = ub_route_mod_neighbor(port, r_port); + if (ret) + return ret; + + ret = ub_route_mod_neighbor(r_port, port); + if (ret) + return ret; + + /* for uent that can't forward, there's no need to update other uent */ + if (ub_entity_support_forward(uent)) + ub_route_mod_bfs(uent); + + if (ub_entity_support_forward(r_uent)) + ub_route_mod_bfs(r_uent); + + ub_route_sync_all(); + return 0; +} + +/** + * a simple example for a new device link up, which is hotplug or reset + * for a given topo like + * +-------------+ +---------+ +---------+ +---------+ + * | controller0 |p0:--:p0| switch0 |p1:--slot0--:p0| switch1 |p1:--:p0| device0 | + * +-------------+ +---------+ +---------+ +---------+ + * when port0 is calling handle link up + * 1. enum at port0 to create switch1 and device0, put them in dev_list + * 2. route dev_list to set up route between these two devices + * 3. handle route link up at port0, add route of left(controller0 & switch0) + * into right(switch1 & device0) and route of right into left + * 4. start switch1 and device0 + */ +static int ublc_handle_new_device_link_up(struct ub_port *port) +{ + struct list_head dev_list; + int ret; + + INIT_LIST_HEAD(&dev_list); + + ret = ublc_enum_at_port(port, &dev_list); + if (ret) { + ub_err(port->uent, "link up enum at port%u failed, ret=%d\n", + port->index, ret); + return ret; + } + + if (list_empty(&dev_list)) { + ub_warn(port->uent, "link up without remote dev\n"); + if (port->link_state == LINK_STATE_RESETING) + port->link_state = LINK_STATE_DONE; + return -ENXIO; + } + + ret = ub_route_entities(&dev_list); + if (ret) { + ub_err(port->uent, "link up cal route failed, ret=%d\n", ret); + goto err_route; + } + + if (port->slot) + port->slot->r_uent = port->r_uent; + + ret = ublc_update_route_link_up(port); + if (ret) { + ub_err(port->uent, "link up update route failed, ret=%d\n", ret); + goto err_link_up; + } + + ret = ub_enum_entities_active(&dev_list); + if (ret) { + ub_err(port->uent, "link up start devices failed, ret=%d\n", ret); + goto err_link_up; + } + + if (port->link_state == LINK_STATE_RESETING) + port->link_state = LINK_STATE_DONE; + + return 0; + +err_link_up: + ublc_update_route_link_down(port); + port->r_uent = NULL; + if (port->slot) + port->slot->r_uent = NULL; +err_route: + ub_enum_clear_ent_list(&dev_list); + return ret; +} + +/** + * ublc_mark_detached_devices() - mark devices as detached and put them into dev_list + * @root: a device that needs to be removed + * @dev_list: a list to store the detached devices + * + * this func use bfs to mark devices and put them into dev_list, all devices + * that connected with root will be marked as detached + */ +static void ublc_mark_detached_devices(struct ub_entity *root, + struct list_head *dev_list) +{ +#define UB_LC_KFIFO_DEPTH SZ_16 + DECLARE_KFIFO(kfifo, struct ub_entity *, UB_LC_KFIFO_DEPTH); + struct ub_entity *uent, *r_uent; + struct ub_port *port; + + INIT_KFIFO(kfifo); + ub_entity_assign_priv_flag(root, UB_ENTITY_DETACHED, true); + kfifo_put(&kfifo, root); + + down_write(&ub_bus_sem); + while (kfifo_get(&kfifo, &uent)) { + for_each_uent_port(port, uent) { + if (!port->r_uent) + continue; + + r_uent = port->r_uent; + if (ub_entity_test_priv_flag(r_uent, UB_ENTITY_DETACHED)) + continue; + + ub_entity_assign_priv_flag(r_uent, UB_ENTITY_DETACHED, true); + if (!kfifo_put(&kfifo, r_uent)) + ub_err(r_uent, "%s kfifo put failed!\n", __func__); + } + + list_del(&uent->node); + list_add_tail(&uent->node, dev_list); + } + up_write(&ub_bus_sem); +} + +/** + * a simple example for link down + * for a given topo like + * +-------------+ +---------+ +---------+ +---------+ + * | controller0 |p0:--:p0| switch0 |p1:--slot0--:p0| switch1 |p1:--:p0| device0 | + * +-------------+ +---------+ +---------+ +---------+ + * when slot0's final port is calling handle link down + * 1. disconnect p1 and p0 so that p1 and p0 is not connected in software + * 2. put switch1 and device0 into dev_list, mark them as detached + * 3. stop switch1 and device0 + * 4. remove switch1 and device0 + * 5. handle route link down at p1, del route of right(switch1 & device0) + * from left(controller0 & switch0) + */ +static void ublc_handle_all_link_down(struct ub_port *port, struct ub_entity *r_uent) +{ + struct list_head dev_list; + + INIT_LIST_HEAD(&dev_list); + ub_port_disconnect(port); + + if (port->slot) + port->slot->r_uent = NULL; + + ublc_mark_detached_devices(r_uent, &dev_list); + ublc_stop_devices(&dev_list); + ublc_remove_devices(&dev_list); + ublc_update_route_link_down(port); +} + +static bool ublc_device_is_down(struct ub_port *port) +{ + struct ub_entity *r_uent = port->r_uent; + struct ub_entity *uent = port->uent; + struct ub_port *tmp; + + for_each_uent_port(tmp, uent) + if (tmp->index != port->index && tmp->r_uent == r_uent) + return false; + + return true; +} + +static void port_link_state_change(struct ub_port *port, struct ub_port *r_port) +{ + if (port->link_state == LINK_STATE_RESETING) + port->link_state = LINK_STATE_DONE; + + if (r_port->link_state == LINK_STATE_RESETING) + r_port->link_state = LINK_STATE_DONE; +} + +void ublc_link_up_handle(struct ub_port *port) +{ + struct ub_entity *uent = port->uent; + struct ub_entity *r_uent; + struct ub_port *r_port; + int ret; + + if (port->r_uent) { + ub_warn(uent, "port%u is already up\n", port->index); + goto link_up_notify; + } + + device_lock(&uent->dev); + + r_uent = ublc_get_port_r_uent(port); + if (!r_uent) { + ret = ublc_handle_new_device_link_up(port); + if (ret) { + ubhp_handle_power(port->slot, false); + } else { + ub_info(uent, "port%u link up and create device\n", + port->index); + ubhp_handle_power(port->slot, true); + } + goto out; + } + + if (!ub_check_and_connect(port, r_uent)) + goto out; + + r_port = port->r_uent->ports + port->r_index; + ret = ub_route_table_set_for_port(port, r_port); + if (ret) { + ub_err(uent, "port%u up set route table failed! ret=%d\n", + port->index, ret); + goto out; + } + + port_link_state_change(port, r_port); + ub_info(uent, "port%u link up\n", port->index); +out: + device_unlock(&uent->dev); +link_up_notify: + ub_notify_share_port(port, UB_PORT_EVENT_LINK_UP); +} + +static void check_entity_disconnected(struct ub_port *port) +{ + struct ub_entity *ent_near = port->uent, *ent_far = port->r_uent, *tmp; + bool is_connected = false; + struct ub_port *port_iter; + u8 val; + + if (port->type == VIRTUAL) + return; + + /* Ensure topo_rank of ent_near is less than topo_rank of ent_far */ + if (ent_near->topo_rank > ent_far->topo_rank) { + tmp = ent_near; + ent_near = ent_far; + ent_far = tmp; + } + + if (ub_entity_test_priv_flag(ent_far, UB_ENTITY_DISCONNECTED)) + return; + + /* Traverse all ports of ent_near and check if they are connected to ent_far */ + for_each_uent_port(port_iter, ent_near) { + if (port_iter->r_uent != ent_far) + continue; + if (ub_port_read_byte(port_iter, + UB_PORT_PHYSICAL_PORT_LINK_STATUS, &val)) { + ub_err(ent_near, "get port[%u] link status failed\n", + port_iter->index); + return; + } + if (val) { + is_connected = true; + break; + } + } + + /* If no connection is detected, mark ent_far as disconnected */ + if (!is_connected) { + ub_info(ent_far, "ub entity is down\n"); + ub_entity_assign_priv_flag(ent_far, UB_ENTITY_DISCONNECTED, true); + } +} + +void ublc_link_down_handle(struct ub_port *port) +{ + struct ub_entity *uent = port->uent; + struct ub_port *r_port; + + if (!port->r_uent) { + ub_warn(uent, "port%u is already down\n", port->index); + goto link_down_notify; + } + + check_entity_disconnected(port); + + device_lock(&uent->dev); + + if (ublc_device_is_down(port)) { + ub_entity_assign_priv_flag(port->r_uent, UB_ENTITY_DISCONNECTED, true); + ub_info(uent, "port%u link down\n", port->index); + ublc_handle_all_link_down(port, port->r_uent); + ub_info(uent, "all port link down and remove device\n"); + device_unlock(&uent->dev); + + goto link_down_notify; + } + + r_port = port->r_uent->ports + port->r_index; + + ub_route_table_clear_for_port(port, r_port); + ub_port_disconnect(port); + + device_unlock(&uent->dev); + + ub_info(uent, "port%u link down\n", port->index); +link_down_notify: + ub_notify_share_port(port, UB_PORT_EVENT_LINK_DOWN); +} + +static void ub_link_handle_event(struct ub_port *port, enum ub_link_event event) +{ + if (event == UB_LINK_UP) + ublc_link_up_handle(port); + else + ublc_link_down_handle(port); +} + +static struct ub_port *ub_link_get_port_from_msg(void *pkt) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct link_msg_payload *payload; + struct ub_port *port = NULL; + struct ub_entity *uent; + u32 seid; + + seid = eid_gen(header->seid_h, header->seid_l); + uent = ub_get_ent_by_eid(seid); + if (!uent) { + pr_warn("get no device by eid %u\n", seid); + return NULL; + } + + payload = (struct link_msg_payload *)header->payload; + if (payload->port_idx >= uent->port_nums) { + pr_err("link port idx %u exceeds uent port num %u\n", + payload->port_idx, uent->port_nums); + goto out; + } + + port = uent->ports + payload->port_idx; + +out: + ub_entity_put(uent); + return port; +} + +static void ub_link_event_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct ub_port *port; + + if (len < UB_LINK_MSG_SIZE) { + dev_err(&ubc->dev, "lc msg len[%#x] invalid\n", len); + return; + } + + port = ub_link_get_port_from_msg(pkt); + if (!port) + return; + + ub_link_handle_event(port, + (enum ub_link_event)header->msgetah.sub_msg_code); +} + +/** + * for an incoming msg, the following procedure is performed: + * 1. parse relating link event from msg + * 2. parse slot/port that has this event + * 3. if hotplug event is button pressed, queue a button work; if hotplug event + * is card presence, queue a present work with 0s delay, if the work + * already exists, modify it's delay time to 0s + */ + +static rx_msg_handler_t link_msg_handler[UB_SUB_MSG_CODE_NUM] = { + ub_link_event_handler, + ub_link_event_handler, + ubhp_event_handler, + ubhp_event_handler, +}; + +void ub_link_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + u8 sub_msg_code = header->msgetah.sub_msg_code; + rx_msg_handler_t handler; + + handler = link_msg_handler[sub_msg_code]; + if (handler) + handler(ubc, pkt, len); + else + dev_err(&ubc->dev, "link sub msg code[%#x] not support\n", + sub_msg_code); +} diff --git a/drivers/ub/ubus/link.h b/drivers/ub/ubus/link.h new file mode 100644 index 0000000000000000000000000000000000000000..d5eaf9b87c2a000fbc37af2f4533a4ee2210fdd0 --- /dev/null +++ b/drivers/ub/ubus/link.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __LINK_H__ +#define __LINK_H__ + +struct link_msg_payload { + /* DW0 */ + u32 scna : 24; + u32 rsv0 : 8; + /* DW1 */ + u32 port_idx : 16; + u32 rsv1 : 16; +}; + +#define UB_LINK_MSG_SIZE 40 + +enum ub_link_event { + UB_LINK_UP, + UB_LINK_DOWN +}; + +void ublc_link_up_handle(struct ub_port *port); +void ublc_link_down_handle(struct ub_port *port); +void ub_link_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +#endif /* __LINK_H__ */ diff --git a/drivers/ub/ubus/memory.c b/drivers/ub/ubus/memory.c new file mode 100644 index 0000000000000000000000000000000000000000..9dc07f7047a241c802528ee6fa905cfa9dec679a --- /dev/null +++ b/drivers/ub/ubus/memory.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus memory: " fmt + +#include +#include "ubus.h" +#include "ubus_controller.h" +#include "memory.h" + +static ubmem_ras_handler handler; + +static bool ub_mem_uent_valid(struct ub_entity *uent) +{ + if (!uent || !uent->ubc) + return false; + + return is_ibus_controller(uent); +} + +void ub_mem_decoder_init(struct ub_entity *uent) +{ + struct ub_bus_controller *ubc; + int ret; + + if (!ub_mem_uent_valid(uent)) + return; + + ubc = uent->ubc; + if (ubc->ops && ubc->ops->mem_decoder_create) { + ret = ubc->ops->mem_decoder_create(ubc); + WARN_ON(ret); + } else { + dev_warn(&ubc->dev, + "ubc ops or ubc ops mem_decoder_create is null.\n"); + } +} + +void ub_mem_decoder_uninit(struct ub_entity *uent) +{ + struct ub_bus_controller *ubc; + + if (!ub_mem_uent_valid(uent)) + return; + + ubc = uent->ubc; + if (ubc->ops && ubc->ops->mem_decoder_remove) + ubc->ops->mem_decoder_remove(ubc); + else + dev_warn(&ubc->dev, "ubc ops mem_decoder_remove is null.\n"); +} + +void ub_mem_ras_handler_register(ubmem_ras_handler rh) +{ + handler = rh; +} +EXPORT_SYMBOL_GPL(ub_mem_ras_handler_register); + +void ub_mem_ras_handler_unregister(void) +{ + handler = NULL; +} +EXPORT_SYMBOL_GPL(ub_mem_ras_handler_unregister); + +ubmem_ras_handler ub_mem_ras_handler_get(void) +{ + return handler; +} +EXPORT_SYMBOL_GPL(ub_mem_ras_handler_get); + +void ub_mem_init_usi(struct ub_entity *uent) +{ + if (!uent->ubc) { + pr_err("ubc not exist, can't init usi\n"); + return; + } + + if (uent->ubc->ops && uent->ubc->ops->register_ubmem_irq) + uent->ubc->ops->register_ubmem_irq(uent->ubc); + else + dev_warn(&uent->ubc->dev, "ubc ops register_ubmem_irq is null.\n"); +} + +void ub_mem_uninit_usi(struct ub_entity *uent) +{ + if (!uent->ubc) { + pr_err("ubc not exist, can't uninit usi\n"); + return; + } + + if (uent->ubc->ops && uent->ubc->ops->unregister_ubmem_irq) + uent->ubc->ops->unregister_ubmem_irq(uent->ubc); + else + dev_warn(&uent->ubc->dev, "ubc ops unregister_ubmem_irq is null.\n"); +} + +void ub_mem_drain_start(u32 scna) +{ + struct ub_mem_device *mem_device; + struct ub_bus_controller *ubc; + + ubc = ub_find_bus_controller_by_cna(scna); + if (!ubc) { + pr_err("No ubc has cna of %u\n", scna); + return; + } + + mem_device = ubc->mem_device; + if (!mem_device) { + dev_err(&ubc->dev, "ubc mem_device is null.\n"); + return; + } + + if (mem_device->ops && mem_device->ops->mem_drain_start) + mem_device->ops->mem_drain_start(ubc); + else + dev_warn(mem_device->dev, "ub mem_device ops mem_drain_start is null.\n"); +} +EXPORT_SYMBOL_GPL(ub_mem_drain_start); + +int ub_mem_drain_state(u32 scna) +{ + struct ub_mem_device *mem_device; + struct ub_bus_controller *ubc; + + ubc = ub_find_bus_controller_by_cna(scna); + if (!ubc) { + pr_err("No ubc has cna of %u\n", scna); + return -ENODEV; + } + + mem_device = ubc->mem_device; + if (!mem_device) { + dev_err(&ubc->dev, "ubc mem_device is null.\n"); + return -EINVAL; + } + + if (mem_device->ops && mem_device->ops->mem_drain_state) + return mem_device->ops->mem_drain_state(ubc); + + dev_warn(mem_device->dev, "ub memory decoder ops mem_drain_state is null.\n"); + return 0; +} +EXPORT_SYMBOL_GPL(ub_mem_drain_state); + +void ub_mem_drain_start_enhanced(void) +{ + struct ub_mem_device *mem_device; + struct ub_bus_controller *ubc; + + list_for_each_entry(ubc, &ubc_list, node) { + mem_device = ubc->mem_device; + if (!mem_device) { + dev_warn(&ubc->dev, "ubc mem_device is null.\n"); + continue; + } + if (mem_device->ops && mem_device->ops->mem_drain_start) + mem_device->ops->mem_drain_start(ubc); + else + dev_warn(&ubc->dev, "ub mem_device ops mem_drain_start is null.\n"); + } +} +EXPORT_SYMBOL_GPL(ub_mem_drain_start_enhanced); + +int ub_mem_drain_state_enhanced(void) +{ + struct ub_mem_device *mem_device; + struct ub_bus_controller *ubc; + int ret = 0; + + list_for_each_entry(ubc, &ubc_list, node) { + mem_device = ubc->mem_device; + if (!mem_device) { + dev_warn(&ubc->dev, "ubc mem_device is null.\n"); + continue; + } + + if (mem_device->ops && mem_device->ops->mem_drain_state) { + ret = mem_device->ops->mem_drain_state(ubc); + if (!ret) + return ret; + } else { + dev_warn(&ubc->dev, "ub memory decoder ops mem_drain_state is null.\n"); + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(ub_mem_drain_state_enhanced); + +int ub_mem_get_numa_id(u32 scna) +{ + struct ub_bus_controller *ubc; + + ubc = ub_find_bus_controller_by_cna(scna); + if (!ubc) { + pr_err("No ubc has cna of %u\n", scna); + return NUMA_NO_NODE; + } + + return pxm_to_node(ubc->attr.proximity_domain); +} +EXPORT_SYMBOL_GPL(ub_mem_get_numa_id); + +bool ub_memory_validate_pa(u32 scna, u64 pa_start, u64 pa_end, bool cacheable) +{ + struct ub_mem_device *mem_device; + struct ub_bus_controller *ubc; + + ubc = ub_find_bus_controller_by_cna(scna); + if (!ubc) { + pr_err("No ubc has cna of %u\n", scna); + return false; + } + + mem_device = ubc->mem_device; + if (!mem_device) { + dev_err(&ubc->dev, "ubc mem_device is null.\n"); + return false; + } + + if (mem_device->ops && mem_device->ops->mem_validate_pa) + return mem_device->ops->mem_validate_pa(ubc, pa_start, pa_end, + cacheable); + + dev_warn(mem_device->dev, "ub memory decoder ops memory_validate_pa is null.\n"); + return false; +} +EXPORT_SYMBOL_GPL(ub_memory_validate_pa); diff --git a/drivers/ub/ubus/memory.h b/drivers/ub/ubus/memory.h new file mode 100644 index 0000000000000000000000000000000000000000..ab6ad0e72ac8091d408a367e3cc7facf57c6537a --- /dev/null +++ b/drivers/ub/ubus/memory.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __MEMORY_H__ +#define __MEMORY_H__ + +#include +#include +#include +#include + +#define MAX_RAS_ERROR_SOURCES_CNT 256 + +void ub_mem_decoder_init(struct ub_entity *uent); +void ub_mem_decoder_uninit(struct ub_entity *uent); +void ub_mem_init_usi(struct ub_entity *uent); +void ub_mem_uninit_usi(struct ub_entity *uent); + +struct ub_mem_ras_err_info { + enum ras_err_type type; + u64 val0; /* addr or transaction id + port bitmap or 0*/ + u64 val1; /* port bitmap or 0 */ +}; + +struct ub_mem_ras_ctx { + DECLARE_KFIFO(ras_fifo, struct ub_mem_ras_err_info, + MAX_RAS_ERROR_SOURCES_CNT); +}; + +struct ub_mem_device_ops { + void (*mem_drain_start)(struct ub_bus_controller *ubc); + int (*mem_drain_state)(struct ub_bus_controller *ubc); + bool (*mem_validate_pa)(struct ub_bus_controller *ubc, u64 pa_start, + u64 pa_end, bool cacheable); + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) + CK_KABI_RESERVE(5) + CK_KABI_RESERVE(6) + CK_KABI_RESERVE(7) + CK_KABI_RESERVE(8) +}; + +struct ub_mem_device { + struct device *dev; + struct ub_entity *uent; + struct ub_mem_ras_ctx ras_ctx; + int ubmem_irq_num; + const struct ub_mem_device_ops *ops; + void *priv_data; + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) +}; + +#endif /* __MEMORY_H__ */ diff --git a/drivers/ub/ubus/msg.c b/drivers/ub/ubus/msg.c new file mode 100644 index 0000000000000000000000000000000000000000..f5a71844107e09934c0aecf2c4c43b841139b3df --- /dev/null +++ b/drivers/ub/ubus/msg.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus msg: " fmt + +#include +#include +#include + +#include "pool.h" +#include "link.h" +#include "msg.h" + +u8 err_to_msg_rsp(int err) +{ + if (err >= 0) + return err; + + switch (err) { + case -ENOMEM: + return UB_MSG_RSP_EXEC_ENOMEM; + case -EACCES: + return UB_MSG_RSP_EXEC_EACCES; + case -EFAULT: + return UB_MSG_RSP_EXEC_EFAULT; + case -EBUSY: + return UB_MSG_RSP_EXEC_EBUSY; + case -ENODEV: + return UB_MSG_RSP_EXEC_ENODEV; + case -EINVAL: + return UB_MSG_RSP_EXEC_EINVAL; + case -ENOEXEC: + return UB_MSG_RSP_EXEC_ENOEXEC; + default: + return UB_MSG_RSP_UNKNOWN; + } +} + +static LIST_HEAD(message_device_list); +static DEFINE_SPINLOCK(message_device_lock); + +int message_device_register(struct message_device *mdev) +{ + spin_lock(&message_device_lock); + list_add_tail(&mdev->list, &message_device_list); + spin_unlock(&message_device_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(message_device_register); + +void message_device_unregister(struct message_device *mdev) +{ + spin_lock(&message_device_lock); + list_del(&mdev->list); + spin_unlock(&message_device_lock); +} +EXPORT_SYMBOL_GPL(message_device_unregister); + +static struct dev_message *dev_message_get(struct ub_entity *uent) +{ + struct dev_message *msg = uent->message; + + if (msg) + return msg; + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return NULL; + + uent->message = msg; + + return msg; +} + +static void dev_message_put(struct ub_entity *uent) +{ + kfree(uent->message); + uent->message = NULL; +} + +int message_probe_device(struct ub_entity *uent) +{ + if (!dev_message_get(uent)) + return -ENOMEM; + + if (!uent->message->mdev) + uent->message->mdev = uent->ubc->mdev; + + return 0; +} + +void message_remove_device(struct ub_entity *uent) +{ + if (!uent->message) + return; + + dev_message_put(uent); +} + +int message_sync_request(struct message_device *mdev, struct msg_info *info, + u8 code) +{ + int ret, i = 1; + + if (mdev->ops->sync_request) { + while (1) { + ret = mdev->ops->sync_request(mdev, info, code); + if (ret != -ETIMEDOUT) + return ret; + + i++; + + if (!msg_retry || i > RETRY_COUNT) + return -ETIMEDOUT; + } + } + + return -ENOTTY; +} + +int message_send(struct message_device *mdev, struct msg_info *info, + u8 code) +{ + if (mdev->ops->send) + return mdev->ops->send(mdev, info, code); + + return -ENOTTY; +} + +int message_response(struct message_device *mdev, struct msg_info *info, + u8 code) +{ + if (mdev->ops->response) + return mdev->ops->response(mdev, info, code); + + return -ENOTTY; +} +EXPORT_SYMBOL_GPL(message_response); + +int message_sync_enum(struct message_device *mdev, struct msg_info *info, + u8 cmd) +{ + int ret, i = 1; + + if (mdev->ops->sync_enum) { + while (1) { + ret = mdev->ops->sync_enum(mdev, info, cmd); + if (ret != -ETIMEDOUT) + return ret; + + i++; + + if (!msg_retry || i > RETRY_COUNT) + return -ETIMEDOUT; + } + } + + return -ENOTTY; +} + +static struct workqueue_struct *rx_msg_wq[UB_MSG_CODE_NUM]; +struct workqueue_struct *get_rx_msg_wq(u8 msg_code) +{ + return rx_msg_wq[msg_code]; +} + +static atomic_t msg_rx_flag; + +int message_rx_init(void) +{ + const char *msg_name[UB_MSG_CODE_NUM] = { + NULL, "link wq", NULL, "vdm wq", + NULL, NULL, "pool wq", NULL + }; + struct workqueue_struct *q; + int i; + + for (i = 0; i < UB_MSG_CODE_NUM; i++) { + if (!msg_name[i]) + continue; + q = create_singlethread_workqueue(msg_name[i]); + if (!q) { + pr_err("alloc workqueue[%d] failed\n", i); + message_rx_uninit(); + return -ENOMEM; + } + + rx_msg_wq[i] = q; + } + + wmb(); /* Ensure the register is written correctly. */ + atomic_set(&msg_rx_flag, 1); + + return 0; +} + +void message_rx_uninit(void) +{ +#define MSG_RX_WAIT_US 15000 + struct workqueue_struct *q; + int i; + + atomic_set(&msg_rx_flag, 0); + wmb(); /* Ensure the register is written correctly. */ + /* For cpus still handle rx msg in interrupt context */ + udelay(MSG_RX_WAIT_US); + + for (i = 0; i < UB_MSG_CODE_NUM; i++) { + q = rx_msg_wq[i]; + if (q) { + flush_workqueue(q); + destroy_workqueue(q); + rx_msg_wq[i] = NULL; + } + } +} + +static struct ub_rx_msg_task * +message_rx_task_alloc_and_init(struct ub_bus_controller *ubc, void *pkt, u16 len, + work_func_t func) +{ + struct ub_rx_msg_task *task; + + task = kzalloc(sizeof(*task), GFP_ATOMIC); + if (!task) + return (struct ub_rx_msg_task *)ERR_PTR(-ENOMEM); + + task->pkt = kzalloc(len, GFP_ATOMIC); + if (!task->pkt) { + kfree(task); + return (struct ub_rx_msg_task *)ERR_PTR(-ENOMEM); + } + + memcpy(task->pkt, pkt, len); + task->len = len; + task->ubc = ubc; + INIT_WORK(&task->work, func); + + return task; +} + +static void message_rx_task_free(struct ub_rx_msg_task *task) +{ + kfree(task->pkt); + kfree(task); +} + +static bool msg_rx_code_valid(struct ub_bus_controller *ubc, u8 code) +{ + u8 msg = msg_code(code); + + if (msg_type(code) == MSG_RSP) + return false; + + switch (msg) { + case UB_MSG_CODE_RAS: + case UB_MSG_CODE_CFG: + case UB_MSG_CODE_EXCH: + case UB_MSG_CODE_SEC: + case UB_MSG_CODE_MAX: + return false; + + case UB_MSG_CODE_POOL: + if (!ubc->cluster) + return false; + break; + + default: + break; + } + + if (!rx_msg_wq[msg]) { + dev_err(&ubc->dev, "no workqueue registered for msg %u\n", msg); + return false; + } + + return true; +} + +static rx_msg_handler_t rx_msg_handler[UB_MSG_CODE_NUM] = { + NULL, + ub_link_msg_handler, + NULL, + NULL, + NULL, + NULL, + ub_pool_rx_msg_handler, +}; + +static void message_rx_work(struct work_struct *work) +{ + struct ub_rx_msg_task *task = container_of(work, struct ub_rx_msg_task, + work); + struct msg_pkt_header *header = (struct msg_pkt_header *)task->pkt; + u8 msg_code = header->msgetah.msg_code; + struct ub_bus_controller *ubc = task->ubc; + rx_msg_handler_t handler; + + handler = rx_msg_handler[msg_code]; + + if (msg_code == UB_MSG_CODE_VDM) + handler = ubc->mdev->ops->vdm_rx_handler; + + if (handler) + handler(ubc, task->pkt, task->len); + else + dev_err(&ubc->dev, "rx msg code not support, code=%#x\n", + msg_code); + + message_rx_task_free(task); +} + +int message_rx_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct msg_extended_header *msgetah = &header->msgetah; + struct ub_rx_msg_task *task; + + if (!atomic_read(&msg_rx_flag)) + return -EBUSY; + + if (len < MSG_PKT_HEADER_SIZE) { + dev_err(&ubc->dev, "rx msg len invalid, len=%#x\n", len); + return -EINVAL; + } + + if (msgetah->plen != len - MSG_PKT_HEADER_SIZE) { + dev_err(&ubc->dev, "rx msg plen invalid, len=%#x, plen=%#x\n", + len, msgetah->plen); + return -EINVAL; + } + + if (!msg_rx_code_valid(ubc, msgetah->code)) { + dev_err(&ubc->dev, "rx msg code invalid, code=%#x\n", + msgetah->code); + return -EINVAL; + } + + dev_info(&ubc->dev, "rx msg coming, code=%#x\n", msgetah->code); + + task = message_rx_task_alloc_and_init(ubc, pkt, len, message_rx_work); + if (IS_ERR(task)) + return PTR_ERR(task); + + queue_work(rx_msg_wq[msgetah->msg_code], &task->work); + return 0; +} +EXPORT_SYMBOL_GPL(message_rx_handler); + +static int handle_vdm_rsp_msg(struct ub_entity *uent, struct ub_vdm_pld *vdm_pld, + struct msg_info *info) +{ + struct msg_pkt_header *header; + + header = (struct msg_pkt_header *)info->rsp_packet; + if (header->msgetah.plen > vdm_pld->rsp_buf_len) { + ub_err(uent, "rsp msg len error, plen=%u, buf_len=%u\n", + header->msgetah.plen, vdm_pld->rsp_buf_len); + return -ENOMEM; + } + + if (header->msgetah.rsp_status) { + ub_err(uent, " vdm rsp msg status error, status=%u\n", + header->msgetah.rsp_status); + return -EINVAL; + } + + vdm_pld->rsp_pld_len = header->msgetah.plen; + if (!vdm_pld->rsp_pld) { + if (!vdm_pld->rsp_pld_len) + return 0; + + ub_err(uent, "rsp_pld is NULL\n"); + return -EINVAL; + } + + memcpy(vdm_pld->rsp_pld, info->rsp_packet + MSG_PKT_HEADER_SIZE, + vdm_pld->rsp_pld_len); + + return 0; +} + +int ub_vdm_message(struct ub_entity *uent, struct ub_vdm_pld *vdm_pld) +{ + struct msg_pkt_header *header; + struct msg_info info = {}; + void *req_pkt; + void *rsp_pkt; + u32 pkt_size; + int ret; + u8 code; + + if (!uent || !uent->message || !uent->message->mdev || !vdm_pld || + !vdm_pld->req_pld || !vdm_pld->req_pld_len) { + pr_err("input data error\n"); + return -EINVAL; + } + + if (vdm_pld->req_pld_len > SZ_1K || + MSG_PKT_HEADER_SIZE + vdm_pld->rsp_buf_len > U16_MAX) { + ub_err(uent, "input vdm msg too long\n"); + return -EINVAL; + } + + req_pkt = kzalloc(MSG_PKT_HEADER_SIZE + vdm_pld->req_pld_len, GFP_ATOMIC); + if (!req_pkt) + return -ENOMEM; + + rsp_pkt = kzalloc(MSG_PKT_HEADER_SIZE + vdm_pld->rsp_buf_len, GFP_ATOMIC); + if (!rsp_pkt) { + kfree(req_pkt); + return -ENOMEM; + } + + code = code_gen(UB_MSG_CODE_VDM, vdm_pld->sub_msg_code, MSG_REQ); + header = (struct msg_pkt_header *)req_pkt; + pkt_size = msg_size_gen((vdm_pld->req_pld_len + MSG_PKT_HEADER_SIZE), + (vdm_pld->rsp_buf_len + MSG_PKT_HEADER_SIZE)); + ub_msg_pkt_header_init(header, uent, vdm_pld->req_pld_len, code, false); + memcpy(req_pkt + MSG_PKT_HEADER_SIZE, vdm_pld->req_pld, + vdm_pld->req_pld_len); + message_info_init(&info, uent, req_pkt, rsp_pkt, pkt_size); + ret = message_sync_request(uent->message->mdev, &info, code); + if (ret) { + ub_err(uent, "message sync execute error, ret=%d\n", ret); + goto out; + } + + ret = handle_vdm_rsp_msg(uent, vdm_pld, &info); + +out: + kfree(req_pkt); + kfree(rsp_pkt); + + return ret; +} +EXPORT_SYMBOL_GPL(ub_vdm_message); diff --git a/drivers/ub/ubus/msg.h b/drivers/ub/ubus/msg.h new file mode 100644 index 0000000000000000000000000000000000000000..088f4e73be6ddbbda2ea0fd9199c146194bc4e90 --- /dev/null +++ b/drivers/ub/ubus/msg.h @@ -0,0 +1,301 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __MSG_H__ +#define __MSG_H__ + +#include "ubus.h" + +extern bool msg_retry; + +struct ub_link_header { + u32 plen : 14; + u32 rt : 2; + u32 cfg : 4; + u32 rsvd1 : 1; + u32 vl : 4; + u32 rsvd0 : 1; + u32 crd_vl : 4; + u32 ack : 1; + u32 crd : 1; +}; +#define UB_COMPACT_LINK_CFG 6 + +struct compact_network_header { + /* DW0 */ + u32 dcna : 16; + u32 scna : 16; + /* DW1 */ +#define NTH_NLP_WITH_TPH 0 +#define NTH_NLP_WITHOUT_TPH 1 + u32 nth_nlp : 3; + u32 mgmt : 1; + u32 sl : 4; + u32 lb : 8; + u32 cc : 16; +}; + +/* message */ +#define seid_high(eid) (((eid) >> 12) & 0xff) +#define seid_low(eid) ((eid) & 0xfff) +#define eid_gen(eid_h, eid_l) ((eid_h) << 12 | (eid_l)) +#define msg_type(code) ((code) & 0x1) +#define msg_code(code) (((code) >> 1) & 0x7) +#define sub_msg_code(code) (((code) >> 4) & 0xf) +#define code_gen(msg, sub_msg, type) ((sub_msg) << 4 | ((msg) << 1 | (type))) +#define ubba_gen(ubba_h, ubba_l) ((u64)(ubba_h) << 32 | (ubba_l)) + +#define RETRY_COUNT 3 +#define PLD_SIZE_MAX SZ_1K + +enum ub_msg_type { + MSG_REQ = 0, + MSG_RSP = 1 +}; + +enum ub_msg_code { + UB_MSG_CODE_RAS = 0, + UB_MSG_CODE_LINK = 1, + UB_MSG_CODE_CFG = 2, + UB_MSG_CODE_VDM = 3, + UB_MSG_CODE_EXCH = 4, + UB_MSG_CODE_SEC = 5, + UB_MSG_CODE_POOL = 6, + UB_MSG_CODE_MAX = 7 +}; + +#define UB_MSG_CODE_NUM 8 +#define UB_SUB_MSG_CODE_NUM 16 + +enum ub_sec_sub_msg_code { + UB_TOKEN_CHECK_CFG = 2, +}; + +enum ub_exch_sub_msg_code { + UB_OBTAIN_ENTITY_INFO = 2, + UB_LINK_NEIGHBOR_QUERY = 7, +}; + +enum ub_pool_sub_msg_code { + UB_BI_CREATE = 2, + UB_BI_DESTROY = 3, +}; + +enum ub_vdm_sub_msg_code { + UB_VENDOR_MSG = 0xf +}; + +enum ub_msg_rsp_status_code { + UB_MSG_RSP_SUCCESS = 0x00, + UB_MSG_RSP_CMD_EACCESS = 0x01, + UB_MSG_RSP_CMD_ENODEV = 0x02, + UB_MSG_RSP_CMD_CODE_ERR = 0x03, + UB_MSG_RSP_CMD_LEN_ERR = 0x04, + /* 0x05~0x1f reserved */ + UB_MSG_RSP_EXEC_ENOMEM = 0x20, + UB_MSG_RSP_EXEC_EACCES = 0x21, + UB_MSG_RSP_EXEC_EFAULT = 0x22, + UB_MSG_RSP_EXEC_EBUSY = 0x23, + UB_MSG_RSP_EXEC_EEXIST = 0x24, + UB_MSG_RSP_EXEC_ENODEV = 0x25, + UB_MSG_RSP_EXEC_EINVAL = 0x26, + UB_MSG_RSP_EXEC_ENOEXEC = 0x27, + /* 0x28~0xfe reserved */ + UB_MSG_RSP_UNKNOWN = 0xff +}; + +u8 err_to_msg_rsp(int err); + +struct msg_extended_header { + u32 plen : 12; + u32 rsvd : 4; + u32 rsp_status : 8; + union { + struct { + u8 type : 1; + u8 msg_code : 3; + u8 sub_msg_code : 4; + }; + u8 code; + }; +}; + +struct msg_pkt_header { + /* DW0 */ + struct ub_link_header ulh; + /* DW1-DW2 */ + struct compact_network_header nth; + /* DW3 */ + u32 seid_h : 8; + u32 upi : 16; +#define CTPH_NLP_UPI_40BITS_UEID 2 + u32 ctph_nlp : 4; /* tp header */ + u32 pad : 2; +#define CTPH_OPCODE_NOT_CNP 0 + u32 tp_opcode : 2; + /* DW4 */ + u32 deid : 20; + u32 seid_l : 12; + /* DW5 */ + u32 src_tassn : 16; + u32 taver : 3; + u32 tk_vld : 1; + u32 udf : 4; +#define TAH_OPCODE_MSG 0x10 + u32 ta_opcode : 8; + /* DW6 */ + u32 msgq_id : 8; + u32 rsvd0 : 24; + /* DW7 */ + struct msg_extended_header msgetah; + + /* DW8~DW11 */ + char payload[]; +}; +#define MSG_PKT_HEADER_SIZE 32 + +#define MSG_CFG_PKT_SIZE 48 /* header 32bytes, pld 16bytes */ + +/** + * struct msg_info - Collection of per message + * + * @ubc: This cfg request source ub bus controller + * @uent: This cfg request target ub_entity + * @req_packet: Request Message packet buffer + * @req_pkt_size: Size of the request packet + * @rsp_packet: Response Message packet buffer + * @rsp_pkt_size: Size of the rsponse packet + * @actual_rsp_size: Size of the actual rsponse packet + * + * vendor can define and parse structure in @req_packet and @rsp_packet + */ +struct msg_info { + struct ub_entity *ubc; + struct ub_entity *uent; + void *req_packet; + u16 req_pkt_size; + void *rsp_packet; + u16 rsp_pkt_size; + u16 actual_rsp_size; +}; +#define MSG_REQ_SIZE_OFFSET 16 + +enum message_tx_type { + MESSAGE_TX_TYPE_MSG, + MESSAGE_TX_TYPE_ENUM +}; + +#define msg_size_gen(req_size, rsp_size) ((u32)((req_size) << 16) | (rsp_size)) + +typedef void (*rx_msg_handler_t)(struct ub_bus_controller *ubc, void *pkt, u16 len); + +/** + * struct message_ops - message ops and capabilities + * @sync_request: send message to target ub_entity and wait response + * @send: send message to target ub_entity but not wait response + * @response: send response message to target + * @sync_enum: send enum message to target ub_entity and wait response + * @vdm_rx_handler: send vdm response message to target + * @owner: Driver module providing these ops + */ +struct message_ops { + int (*sync_request)(struct message_device *mdev, struct msg_info *info, + u8 code); + int (*send)(struct message_device *mdev, struct msg_info *info, + u8 code); + int (*response)(struct message_device *mdev, struct msg_info *info, + u8 code); + int (*sync_enum)(struct message_device *mdev, struct msg_info *info, + u8 cmd); + rx_msg_handler_t vdm_rx_handler; + struct module *owner; +}; + +/** + * struct message_device - Message core representation of one message-dev + * hardware instance + * @list: Used by the message-core to keep a list of registered message-devs + * @ops: Message-ops for talking to this message-dev + * @fwnode: Firmware representation of one message-dev + */ +struct message_device { + struct list_head list; + const struct message_ops *ops; + struct fwnode_handle *fwnode; +}; + +/** + * struct dev_message - Collection of per-device Message-dev data + * + * @mdev: Message device this device is linked to + * @priv: Message Driver private data + */ +struct dev_message { + struct message_device *mdev; + void *priv; +}; + +static inline void message_info_init(struct msg_info *info, struct ub_entity *uent, + void *req, void *rsp, u32 size) +{ + if (uent) { + info->ubc = uent->ubc->uent; + info->uent = uent; + } + info->req_packet = req; + info->rsp_packet = rsp; + /* High 16bits for req size, low 16bits for rsp size */ + info->req_pkt_size = (u16)(size >> MSG_REQ_SIZE_OFFSET); + info->rsp_pkt_size = (u16)size; +} + +static inline void __message_device_set_ops(struct message_device *mdev, + const struct message_ops *ops) +{ + mdev->ops = ops; +} + +#define message_device_set_ops(mdev, ops) \ +do { \ + struct message_ops *__ops = (struct message_ops *)(ops); \ + __ops->owner = THIS_MODULE; \ + __message_device_set_ops(mdev, __ops); \ +} while (0) + +static inline void message_device_set_fwnode(struct message_device *mdev, + struct fwnode_handle *fwnode) +{ + mdev->fwnode = fwnode; +} + +void ub_msg_pkt_header_init(struct msg_pkt_header *header, struct ub_entity *uent, + u16 plen, u8 code, bool flag); + +int message_device_register(struct message_device *mdev); +void message_device_unregister(struct message_device *mdev); +int message_probe_device(struct ub_entity *uent); +void message_remove_device(struct ub_entity *uent); +int message_sync_request(struct message_device *mdev, struct msg_info *info, + u8 code); +int message_send(struct message_device *mdev, struct msg_info *info, + u8 code); +int message_response(struct message_device *mdev, struct msg_info *info, + u8 code); +int message_sync_enum(struct message_device *mdev, struct msg_info *info, + u8 cmd); + +struct ub_rx_msg_task { + struct ub_bus_controller *ubc; + void *pkt; + u16 len; + struct work_struct work; +}; + +struct workqueue_struct *get_rx_msg_wq(u8 msg_code); +int message_rx_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); +int message_rx_init(void); +void message_rx_uninit(void); + +#endif /* __MSG_H__ */ diff --git a/drivers/ub/ubus/msi/Makefile b/drivers/ub/ubus/msi/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9fb340462fc6bdf1d86f8730924cee73ef1e423a --- /dev/null +++ b/drivers/ub/ubus/msi/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_UB_UBUS_USI) += irqdomain.o msi.o diff --git a/drivers/ub/ubus/msi/irqdomain.c b/drivers/ub/ubus/msi/irqdomain.c new file mode 100644 index 0000000000000000000000000000000000000000..83da28e03fc76fb7018461474879b09cdb179cc7 --- /dev/null +++ b/drivers/ub/ubus/msi/irqdomain.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include +#include +#include + +static irq_hw_number_t ub_msi_domain_calc_hwirq(struct msi_desc *desc) +{ + struct ub_entity *uent = msi_desc_to_ub_entity(desc); + + return (irq_hw_number_t)(desc->msi_index | (uent->intr_device_id << 11)); +} + +static void ub_msi_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) +{ + arg->desc = desc; + arg->hwirq = ub_msi_domain_calc_hwirq(desc); + pr_info("%s, arg->hwirq: %lu.\n", __func__, arg->hwirq); +} + +static struct msi_domain_ops ub_msi_domain_ops_default = { + .set_desc = ub_msi_domain_set_desc, +}; + +static void ub_msi_domain_update_dom_ops(struct msi_domain_info *info) +{ + struct msi_domain_ops *ops = info->ops; + + if (ops == NULL) + info->ops = &ub_msi_domain_ops_default; + else if (!ops->set_desc) + ops->set_desc = ub_msi_domain_set_desc; +} + +static void ub_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg) +{ + struct msi_desc *desc = irq_data_get_msi_desc(irq_data); + + if (desc->irq == irq_data->irq) + __ub_write_msi_msg(desc, msg); +} + +static void ub_msi_domain_update_chip_ops(struct msi_domain_info *info) +{ + struct irq_chip *chip = info->chip; + + WARN_ON_ONCE(!chip); + chip->irq_write_msi_msg = chip->irq_write_msi_msg ? + chip->irq_write_msi_msg : + ub_msi_domain_write_msg; + chip->irq_mask = chip->irq_mask ? chip->irq_mask : ub_msi_mask_irq; + chip->irq_unmask = chip->irq_unmask ? chip->irq_unmask : + ub_msi_unmask_irq; +} + +struct irq_domain *ub_msi_create_irq_domain(struct fwnode_handle *fwnode, + struct msi_domain_info *info, + struct irq_domain *parent) +{ + struct irq_domain *domain; + + if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) + ub_msi_domain_update_dom_ops(info); + if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) + ub_msi_domain_update_chip_ops(info); + if (WARN_ON(info->flags & MSI_FLAG_LEVEL_CAPABLE)) + info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; + + info->flags |= MSI_FLAG_FREE_MSI_DESCS | MSI_FLAG_ACTIVATE_EARLY | + MSI_FLAG_DEV_SYSFS; + + if (IS_ENABLED(CONFIG_GENERIC_IRQ_RESERVATION_MODE)) + info->flags |= MSI_FLAG_MUST_REACTIVATE; + + info->chip->flags |= IRQCHIP_ONESHOT_SAFE; + info->bus_token = DOMAIN_BUS_UB_MSI; + + domain = msi_create_irq_domain(fwnode, info, parent); + + return domain; +} +EXPORT_SYMBOL_GPL(ub_msi_create_irq_domain); + +static bool ub_create_device_domain(struct ub_entity *uent, + const struct msi_domain_template *tmpl, + unsigned int hwsize) +{ + struct irq_domain *domain = dev_get_msi_domain(&uent->dev); + + if (!domain || !irq_domain_is_msi_parent(domain)) + return true; + + if (WARN_ON_ONCE(1)) + pr_err("Create device irq domain failed.\n"); + + return false; +} + +bool ub_setup_usi_device_domain(struct ub_entity *uent, unsigned int hwsize) +{ + if (WARN_ON_ONCE(uent->intr_enabled)) + return false; + + if (msi_match_device_irq_domain(&uent->dev, MSI_DEFAULT_DOMAIN, + DOMAIN_BUS_UB_MSI)) + return true; + + return ub_create_device_domain(uent, NULL, hwsize); +} +EXPORT_SYMBOL_GPL(ub_setup_usi_device_domain); diff --git a/drivers/ub/ubus/msi/msi.c b/drivers/ub/ubus/msi/msi.c new file mode 100644 index 0000000000000000000000000000000000000000..f505ad16dcafa67b0e621da7ad8de68d93ad63fc --- /dev/null +++ b/drivers/ub/ubus/msi/msi.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Only support for irqdomain.c. + */ + +#include +#include +#include +#include +#include + +unsigned int ub_irq_calc_affinity_vectors(unsigned int minvec, + unsigned int maxvec, + const struct irq_affinity *affd) +{ + return irq_calc_affinity_vectors(minvec, maxvec, affd); +} +EXPORT_SYMBOL_GPL(ub_irq_calc_affinity_vectors); + +struct ub_device_id_manager { + struct idr device_id_idr; + spinlock_t lock; +}; + +static struct ub_device_id_manager ub_entity_idm = { + .device_id_idr = IDR_INIT(ub_entity_idm.device_id_idr), + .lock = __SPIN_LOCK_UNLOCKED(ub_entity_idm.lock), +}; + +int ub_interrupt_id_alloc(struct ub_entity *uent) +{ + int id; + + idr_preload(GFP_KERNEL); + spin_lock(&ub_entity_idm.lock); + id = idr_alloc(&ub_entity_idm.device_id_idr, uent, + uent->ubc->attr.int_id_start, uent->ubc->attr.int_id_end, + GFP_NOWAIT); + spin_unlock(&ub_entity_idm.lock); + idr_preload_end(); + + if (id < 0) + return id; + uent->intr_device_id = id; + + return 0; +} + +void ub_interrupt_id_free(struct ub_entity *uent) +{ + spin_lock(&ub_entity_idm.lock); + idr_remove(&ub_entity_idm.device_id_idr, uent->intr_device_id); + spin_unlock(&ub_entity_idm.lock); +} +EXPORT_SYMBOL_GPL(ub_interrupt_id_free); + +struct ub_entity *msi_desc_to_ub_entity(struct msi_desc *desc) +{ + return to_ub_entity(desc->dev); +} + +void __iomem *ub_vector_desc_addr(struct msi_desc *desc) +{ + return desc->ub_intr.vector_base + (desc->ub_intr.intr_attrib.entry_nr * + UB_INTR_VECTOR_ENTRY_SIZE); +} + +static void __iomem *ub_addr_desc_base_addr(struct msi_desc *desc) +{ + return desc->ub_intr.addr_base; +} + +static void ub_int_type1_update_mask(struct msi_desc *desc, u32 clear, u32 set) +{ + raw_spinlock_t *lock = &msi_desc_to_ub_entity(desc)->usi_lock; + unsigned long flags; + + raw_spin_lock_irqsave(lock, flags); + desc->ub_intr.intr_attrib.mask &= ~clear; + desc->ub_intr.intr_attrib.mask |= set; + ub_cfg_write_dword(msi_desc_to_ub_entity(desc), UB_INT_TYPE1_INT_MASK, + desc->ub_intr.intr_attrib.mask); + raw_spin_unlock_irqrestore(lock, flags); +} + +static void ub_int_type1_mask(struct msi_desc *desc, u32 mask) +{ + ub_int_type1_update_mask(desc, 0, mask); +} + +static void ub_int_type2_mask(struct msi_desc *desc) +{ + void __iomem *addr = ub_vector_desc_addr(desc); + u16 addr_index; + u32 reg_val; + + desc->ub_intr.intr_attrib.mask = 1; + addr_index = desc->ub_intr.intr_attrib.addr_index; + reg_val = UB_INTR_VECTOR_MASK_MASK | (u32)addr_index; + + writel(reg_val, addr + UB_INTR_VECTOR_ADDR_INDEX); +} + +static void ub_usi_desc_mask(struct msi_desc *desc, u32 nr) +{ + if (desc->ub_intr.intr_attrib.is_type1) + ub_int_type1_mask(desc, nr); + else + ub_int_type2_mask(desc); +} + +void ub_msi_mask_irq(struct irq_data *data) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + + ub_usi_desc_mask(desc, (u32)BIT(data->irq - desc->irq)); +} + +static void ub_int_type1_unmask_irq(struct msi_desc *desc, u32 mask) +{ + ub_int_type1_update_mask(desc, mask, 0); +} + +static void ub_int_type2_unmask_irq(struct msi_desc *desc) +{ + void __iomem *addr = ub_vector_desc_addr(desc); + u16 addr_index; + + desc->ub_intr.intr_attrib.mask = 0; + addr_index = desc->ub_intr.intr_attrib.addr_index; + writel((u32)addr_index, addr + UB_INTR_VECTOR_ADDR_INDEX); +} + +static void ub_usi_desc_unmask(struct msi_desc *desc, u32 nr) +{ + if (desc->ub_intr.intr_attrib.is_type1) + ub_int_type1_unmask_irq(desc, nr); + else + ub_int_type2_unmask_irq(desc); +} + +void ub_msi_unmask_irq(struct irq_data *data) +{ + struct msi_desc *desc = irq_data_get_msi_desc(data); + + ub_usi_desc_unmask(desc, (u32)BIT(data->irq - desc->irq)); +} + +static void __iomem *ub_intr_find_one_addr(struct msi_msg *msg, + struct msi_desc *entry, + u8 *is_new) +{ + void __iomem *first_unused_base = NULL; + void __iomem *base; + u32 addr_l, addr_h, valid, i; + u16 first_unused_id; + u32 addr_cnt = entry->ub_intr.addr_num + 1; + + base = ub_addr_desc_base_addr(entry); + + for (i = 0; i < addr_cnt; i++, base += UB_INTR_ADDR_ENTRY_SIZE) { + valid = readl(base + UB_INTR_ADDR_TOKENID) & UB_INTR_ADDR_VALID_MASK; + if (!valid) { + if (!first_unused_base) { + first_unused_base = base; + first_unused_id = i; + } + continue; + } + addr_l = readl(base + UB_INTR_ADDR_ADDR_L); + addr_h = readl(base + UB_INTR_ADDR_ADDR_H); + if (msg->address_lo == addr_l && msg->address_hi == addr_h) { + entry->ub_intr.intr_attrib.addr_index = i; + *is_new = 0; + return base; + } + } + + if (!first_unused_base) + return NULL; + *is_new = 1; + + entry->ub_intr.intr_attrib.addr_index = first_unused_id; + + return first_unused_base; +} + +static void ub_int_type1_clear_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + struct ub_entity *uent = msi_desc_to_ub_entity(entry); + + ub_cfg_write_byte(uent, UB_INT_TYPE1_ENABLE, 0); + ub_cfg_write_dword(uent, UB_INT_TYPE1_EN_INT_NUM, 0); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_DATA, 0); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_ADDR_L, 0); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_ADDR_H, 0); +} + +static void ub_int_type2_clear_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + void __iomem *vector_base; + + vector_base = ub_vector_desc_addr(entry); + if (!vector_base) { + WARN_ON(1); + return; + } + + writel(0, vector_base + UB_INTR_VECTOR_ID); + /* clear interrupt index(bit 0~15), keep mask(bit 16) */ + writel(UB_INTR_VECTOR_MASK_MASK, + vector_base + UB_INTR_VECTOR_ADDR_INDEX); +} + +static void __ub_clear_msi_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + if (entry->ub_intr.intr_attrib.is_type1) + ub_int_type1_clear_msg(entry, msg); + else + ub_int_type2_clear_msg(entry, msg); +} + +static void ub_int_type1_set_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + struct ub_entity *uent = msi_desc_to_ub_entity(entry); + + ub_cfg_write_byte(uent, UB_INT_TYPE1_ENABLE, 1); + ub_cfg_write_dword(uent, UB_INT_TYPE1_EN_INT_NUM, + (u32)ilog2(__roundup_pow_of_two(entry->nvec_used))); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_DATA, msg->data); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_ADDR_L, msg->address_lo); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_ADDR_H, msg->address_hi); +} + +static void ub_int_type2_set_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + struct ub_entity *uent = msi_desc_to_ub_entity(entry); + void __iomem *vector_base, *addr_base; + unsigned long val; + u32 reg_val; + u8 is_new; + + vector_base = ub_vector_desc_addr(entry); + if (!vector_base) { + WARN_ON(1); + return; + } + + addr_base = ub_intr_find_one_addr(msg, entry, &is_new); + if (addr_base) { + writel(msg->data, vector_base + UB_INTR_VECTOR_ID); + reg_val = readl(vector_base + UB_INTR_VECTOR_ADDR_INDEX) & + ~UB_INTR_VECTOR_ADDR_INDEX_MASK; + reg_val |= entry->ub_intr.intr_attrib.addr_index; + writel(reg_val, vector_base + UB_INTR_VECTOR_ADDR_INDEX); + if (is_new) { + writel(msg->address_lo, addr_base + UB_INTR_ADDR_ADDR_L); + writel(msg->address_hi, addr_base + UB_INTR_ADDR_ADDR_H); + writel(ub_get_dst_eid(uent), addr_base + UB_INTR_ADDR_DSTEID_0); + val = uent->tid & UB_INTR_ADDR_TOKENID_MASK; + writel(val, addr_base + UB_INTR_ADDR_TOKENID); + /* valid bit must be placed at the end. */ + set_bit(UB_INTR_ADDR_VALID_BIT, &val); + writel(val, addr_base + UB_INTR_ADDR_TOKENID); + } + } else { + WARN_ON(1); + } +} + +static void __ub_set_msi_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + if (entry->ub_intr.intr_attrib.is_type1) + ub_int_type1_set_msg(entry, msg); + else + ub_int_type2_set_msg(entry, msg); +} + +void ub_write_interruptid(struct ub_entity *uent) +{ + if (!uent->intr_type1) + ub_cfg_write_dword(uent, UB_INT_ID, uent->intr_device_id); + else + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_ID, uent->intr_device_id); +} + +void __ub_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) +{ + bool unmasked = false; + + if (!entry->ub_intr.intr_attrib.is_type1) + unmasked = !(entry->ub_intr.intr_attrib.mask); + + if (unmasked) + ub_usi_desc_mask(entry, 0); + + if (msg->address_lo == 0 && msg->address_hi == 0) + __ub_clear_msi_msg(entry, msg); + else + __ub_set_msi_msg(entry, msg); + + if (unmasked) + ub_usi_desc_unmask(entry, 0); + + entry->msg = *msg; + if (entry->write_msi_msg) + entry->write_msi_msg(entry, entry->write_msi_msg_data); +} + +u32 ub_intr_addr_count(struct ub_entity *uent) +{ + u32 addr_num = 0; + int ret; + + ret = ub_cfg_read_word(uent, UB_NUM_OF_INTR_ADDR_TBL, (u16 *)&addr_num); + if (ret) + return 0; + /* + * The value of this field is N, indicating that the interrupt address table + * supported by the function entity supports N+1 interrupt address. + */ + addr_num += 1; + + return addr_num; +} +EXPORT_SYMBOL_GPL(ub_intr_addr_count); + +u32 ub_intr_vec_count(struct ub_entity *uent) +{ + u32 vector_num = 0; + int ret; + + ret = ub_cfg_read_word(uent, UB_NUM_OF_INTR_VECTOR_TBL, (u16 *)&vector_num); + if (ret) + return 0; + /* + * The value of this field is N, indicating that the interrupt vector table + * supported by the function entity supports N+1 interrupt vectors. + */ + vector_num += 1; + + return vector_num; +} +EXPORT_SYMBOL_GPL(ub_intr_vec_count); + +static int ub_prepare_msi_desc(struct ub_entity *uent, struct msi_desc *desc) +{ + void __iomem *vec_desc_addr; + u32 addr_cnt, vec_cnt; + + addr_cnt = ub_intr_addr_count(uent); + if (addr_cnt == 0) + return -EINVAL; + + vec_cnt = ub_intr_vec_count(uent); + if (vec_cnt == 0) + return -EINVAL; + + desc->nvec_used = 1; + desc->ub_intr.intr_attrib.is_type1 = 0; + desc->ub_intr.addr_base = uent->intr_addr_base; + desc->ub_intr.vector_base = uent->intr_vector_base; + desc->ub_intr.addr_num = (u16)(addr_cnt - 1); + desc->ub_intr.vec_num = (u16)(vec_cnt - 1); + + vec_desc_addr = ub_vector_desc_addr(desc); + desc->ub_intr.intr_attrib.mask = !!(readl(vec_desc_addr + UB_INTR_VECTOR_ADDR_INDEX) & + UB_INTR_VECTOR_MASK_MASK); + + return 0; +} + +static int ub_setup_msi_descs(struct ub_entity *uent, + struct ub_usi_entry *entries, int nvec, + struct irq_affinity_desc *masks) +{ + struct irq_affinity_desc *curmsk; + struct msi_desc desc; + int ret = 0, i; + + memset(&desc, 0, sizeof(desc)); + + if (uent->intr_type1) { + desc.ub_intr.intr_attrib.is_type1 = 1; + desc.nvec_used = nvec; + desc.affinity = masks; + ret = ub_cfg_read_dword(uent, UB_INT_TYPE1_INT_MASK, + &desc.ub_intr.intr_attrib.mask); + if (ret) + return ret; + + return msi_insert_msi_desc(&uent->dev, &desc); + } + + for (i = 0, curmsk = masks; i < nvec; i++, curmsk++) { + desc.msi_index = entries ? entries[i].entry : i; + desc.ub_intr.intr_attrib.entry_nr = desc.msi_index; + desc.affinity = masks ? curmsk : NULL; + ret = ub_prepare_msi_desc(uent, &desc); + if (ret) + break; + ret = msi_insert_msi_desc(&uent->dev, &desc); + if (ret) + break; + } + return ret; +} + +static int ub_setup_intr_irqs(struct ub_entity *uent, int nvec) +{ + struct irq_domain *domain; + + domain = dev_get_msi_domain(&uent->dev); + if (domain && irq_domain_is_hierarchy(domain)) + return msi_domain_alloc_irqs_all_locked(&uent->dev, 0, nvec); + + pr_info("default irq alloc to be done...\n"); + + return -ENOSPC; +} + +static void ub_update_entries(struct ub_entity *uent, struct ub_usi_entry *entries) +{ + struct msi_desc *desc; + + if (entries) { + msi_for_each_desc(desc, &uent->dev, MSI_DESC_ALL) { + entries->vector = desc->irq; + entries++; + } + } +} + +static void ub_msi_teardown_msi_irqs(struct ub_entity *uent) +{ + struct irq_domain *domain; + + domain = dev_get_msi_domain(&uent->dev); + if (domain && irq_domain_is_hierarchy(domain)) + msi_domain_free_irqs_all_locked(&uent->dev, 0); + else + pr_info("default irq free to be done.\n"); +} + +static void free_msi_irqs(struct ub_entity *uent) +{ + ub_msi_teardown_msi_irqs(uent); + + if (uent->intr_addr_base) { + iounmap(uent->intr_addr_base); + uent->intr_addr_base = NULL; + } + if (uent->intr_vector_base) { + iounmap(uent->intr_vector_base); + uent->intr_vector_base = NULL; + } +} + +int usi_setup_interrupts(struct ub_entity *uent, struct ub_usi_entry *entries, + int nvec, struct irq_affinity *affd) +{ + struct irq_affinity_desc *masks = NULL; + int ret; + + if (affd) + masks = irq_create_affinity_masks(nvec, affd); + + msi_lock_descs(&uent->dev); + ret = ub_setup_msi_descs(uent, entries, nvec, masks); + if (ret) + goto out_free; + + ret = ub_setup_intr_irqs(uent, nvec); + if (ret) + goto out_free; + + ub_update_entries(uent, entries); + goto out_unlock; + +out_free: + free_msi_irqs(uent); +out_unlock: + msi_unlock_descs(&uent->dev); + kfree(masks); + return ret; +} +EXPORT_SYMBOL_GPL(usi_setup_interrupts); + +void ub_disable_intr(struct ub_entity *uent) +{ + u32 i, addr_num; + void *intr_addr; + + if (!uent || !uent->intr_enabled) + return; + + if (uent->intr_type1) { + ub_cfg_write_byte(uent, UB_INT_TYPE1_ENABLE, 0); + ub_cfg_write_dword(uent, UB_INT_TYPE1_INT_MASK, GENMASK(31, 0)); + } else { + addr_num = ub_intr_addr_count(uent); + for (i = 0; i < addr_num; i++) { + intr_addr = uent->intr_addr_base + i * UB_INTR_ADDR_ENTRY_SIZE; + writel(0, intr_addr + UB_INTR_ADDR_TOKENID); + writel(0, intr_addr + UB_INTR_ADDR_DSTEID_0); + writel(0, intr_addr + UB_INTR_ADDR_ADDR_L); + writel(0, intr_addr + UB_INTR_ADDR_ADDR_H); + } + + ub_cfg_write_byte(uent, UB_INT_MASK, 1); + ub_cfg_write_byte(uent, UB_INT_EN, 0); + } + + uent->intr_enabled = 0; + ub_interrupt_id_free(uent); + free_msi_irqs(uent); +} +EXPORT_SYMBOL_GPL(ub_disable_intr); + +int ub_setup_msi_context(struct ub_entity *uent) +{ + return msi_setup_device_data(&uent->dev); +} +EXPORT_SYMBOL_GPL(ub_setup_msi_context); diff --git a/drivers/ub/ubus/pool.c b/drivers/ub/ubus/pool.c new file mode 100644 index 0000000000000000000000000000000000000000..38a8cf76c181075084b825ccc151dce7df96ff4b --- /dev/null +++ b/drivers/ub/ubus/pool.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus pool: " fmt + +#include "ubus.h" +#include "ubus_controller.h" +#include "ubus_entity.h" +#include "resource.h" +#include "msg.h" +#include "enum.h" +#include "port.h" +#include "reset.h" +#include "instance.h" +#include "ubus_inner.h" +#include "pool.h" + +struct cfg_cpl_notify_pld { + u32 flag : 1; + u32 rsvd : 31; + u32 guid[UB_GUID_DW_NUM]; + u32 eid[4]; +}; +#define CFG_CPL_NOTIFY_PLD_SIZE 36 + +struct bi_create_pld { + u32 guid[UB_GUID_DW_NUM]; + u32 eid[4]; + u32 upi : 15; + u32 rsvd1 : 17; +}; +#define BI_CREATE_PLD_SIZE 36 + +struct bi_destroy_pld { + u32 guid[UB_GUID_DW_NUM]; +}; +#define BI_DESTROY_PLD_SIZE 16 + +struct pool_msg_pkt { + struct msg_pkt_header header; + union { + struct entity_reg_msg_pld reg; + struct entity_rls_msg_pld rls; + struct cfg_cpl_notify_pld notify; + struct bi_create_pld create; + struct bi_destroy_pld destroy; + struct port_reset_notify_pld port_reset; + }; +}; + +#define MSG_ENTITY_REG_SIZE (MSG_PKT_HEADER_SIZE + ENTITY_REG_PLD_SIZE) +#define MSG_ENTITY_RLS_SIZE (MSG_PKT_HEADER_SIZE + ENTITY_RLS_PLD_SIZE) +#define MSG_BI_CREATE_SIZE (MSG_PKT_HEADER_SIZE + BI_CREATE_PLD_SIZE) +#define MSG_BI_DESTROY_SIZE (MSG_PKT_HEADER_SIZE + BI_DESTROY_PLD_SIZE) +#define MSG_CFG_CPL_NOTIFY_SIZE (MSG_PKT_HEADER_SIZE + CFG_CPL_NOTIFY_PLD_SIZE) +#define MSG_PORT_RESET_SIZE (MSG_PKT_HEADER_SIZE + PORT_RESET_NOTIFY_PLD_SIZE) + +static DEFINE_SPINLOCK(ub_fad_lock); + +struct ub_fad_collection { + struct list_head list; + struct list_head node; +}; + +static struct ub_fad_collection ub_fad = { + .list = LIST_HEAD_INIT(ub_fad.list), + .node = LIST_HEAD_INIT(ub_fad.node), +}; + +static struct pool_fad *ub_get_fad(u32 eid) +{ + struct pool_fad *fad; + unsigned long flags; + + spin_lock_irqsave(&ub_fad_lock, flags); + list_for_each_entry(fad, &ub_fad.node, node) + if (fad->base.eid[0] == eid) + goto out; + + fad = NULL; +out: + spin_unlock_irqrestore(&ub_fad_lock, flags); + return fad; +} + +struct ub_entity *ub_get_fad_ent_by_eid(unsigned int eid) +{ + struct pool_fad *fad; + + fad = ub_get_fad(eid); + if (fad) + return fad->uent; + + return NULL; +} + +static int ub_fad_res_init(struct pool_fad *fad, struct ub_entity *uent) +{ + u32 source_support_map[MAX_UB_RES_NUM] = { UB_ERS0S_SUPPORT, + UB_ERS1S_SUPPORT, + UB_ERS2S_SUPPORT }; + u32 support_feature = 0; + size_t pg_size; + u8 sys_pg = 0; + int ret, i; + + if (!fad->ers_valid) + return 0; + + ret = ub_cfg_read_byte(uent, UB_SYS_PGS, &sys_pg); + if (ret) { + ub_err(uent, "entity reg read UB_SYS_PGS failed, ret=%d\n", ret); + return ret; + } + pg_size = (sys_pg & UB_SYS_PGS_SIZE) ? SZ_64K : SZ_4K; + + ret = ub_cfg_read_dword(uent, UB_CFG1_SUPPORT_FEATURE_L, &support_feature); + if (ret) { + ub_err(uent, "read cfg1 feature_l failed, ret=%d\n", ret); + return ret; + } + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (!(support_feature & source_support_map[i])) + continue; + + uent->zone[i].res.start = ubba_gen(fad->ers[i].sa_h, + fad->ers[i].sa_l); + uent->zone[i].res.end = uent->zone[i].res.start + + ((u64)fad->ers[i].ss * pg_size - 1); + uent->zone[i].res.flags = IORESOURCE_MEM; + uent->zone[i].res.name = ub_name(uent); + uent->zone[i].sa_used = 1; + uent->zone[i].ubba_used = 0; + ub_info(uent, "mmio_idx=%d, res=%pR\n", i, &uent->zone[i].res); + + ret = ub_insert_resource(uent, i); + if (ret) { + ub_err(uent, "mmio[%d] insert res failed, ret=%d\n", i, + ret); + goto fail; + } + + ub_info(uent, "MMIO[%d]: ubba=0x0, size=%#llx, hpa=%#llx, wr_attr=0, prefetchable=0, order_type=0\n", + i, ub_resource_len(uent, i), uent->zone[i].res.start); + uent->zone[i].init_succ = 1; + } + + return 0; + +fail: + for (i -= 1; i >= 0; i--) + if (uent->zone[i].init_succ) + ub_entity_free_mmio_idx(uent, i); + return ret; +} + +static void ub_fad_uent_init(struct pool_fad *fad, struct ub_entity *uent) +{ + uent->pool = true; + uent->eid = fad->base.eid[0]; + uent->user_eid = fad->base.ueid[0]; + uent->cna = fad->base.cna; + uent->upi = fad->base.upi; + uent->entity_idx = fad->base.entity_idx; + uent->ubc = ub_ubc_get(fad->ubc); + memcpy(uent->guid.dw, fad->base.guid, UB_GUID_SIZE); + uent->pue = uent; + uent->is_mue = 1; +} + +static int ub_fad_attach(struct pool_fad *fad) +{ + struct ub_entity *uent; + int ret; + + if (fad->attach) + return 0; + + uent = ub_alloc_ent(); + if (!uent) + return -ENOMEM; + + ub_fad_uent_init(fad, uent); + + ret = ub_setup_ent(uent); + if (ret) + goto fail; + + ret = ub_fad_res_init(fad, uent); + WARN_ON(ret); + + ub_entity_add(uent, uent->ubc); + + fad->uent = uent; + fad->attach = true; + return 0; +fail: + ub_ubc_put(uent->ubc); + kfree(uent); + return ret; +} + +static void ub_fad_detach(struct pool_fad *fad) +{ + ub_stop_and_remove_ent(fad->uent); + fad->uent = NULL; +} + +bool ub_rsp_msg_init(struct msg_pkt_header *header, u8 status, u32 plen) +{ + struct compact_network_header *cnth = &header->nth; + u32 seid = header->deid; + u32 deid = eid_gen(header->seid_h, header->seid_l); + u16 dcna = cnth->scna; + u16 scna = cnth->dcna; + + cnth->scna = scna; + cnth->dcna = dcna; + + header->seid_h = seid_high(seid); + header->seid_l = seid_low(seid); + header->deid = deid; + + header->msgetah.plen = plen; + header->msgetah.rsp_status = status; + header->msgetah.type = MSG_RSP; + + return (scna == dcna); +} +EXPORT_SYMBOL_GPL(ub_rsp_msg_init); + +static void ub_fad_para_init(struct pool_fad *fad, struct entity_reg_msg_pld *pld) +{ + struct ub_guid *guid = (struct ub_guid *)fad->base.guid; + char buf[SZ_64] = {}; + + memcpy(&fad->base, &pld->base, sizeof(struct entity_base_info)); + + (void)ub_show_guid(guid, buf); + + dev_info(&fad->ubc->dev, + "fad_add_idx=%u, eid=%#x, cna=%#x, upi=%#x, u_eid=%#05x, guid=%s\n", + fad->base.entity_idx, fad->base.eid[0], fad->base.cna, + fad->base.upi, fad->base.ueid[0], buf); + + if (fad->ers_valid) + memcpy(fad->ers, pld->ers, ENTITY_RS_PLD_SIZE); +} + +static int ub_entity_reg_check(struct ub_bus_controller *ubc, + struct entity_reg_msg_pld *pld, bool ers_valid) +{ + struct ub_guid *guid = (struct ub_guid *)pld->base.guid; + struct device *dev = &ubc->dev; + struct entity_rs_info *ers; + int i; + + if (pld->base.eid[0] == 0 || pld->base.ueid[0] == 0) { + dev_err(dev, "entity reg eid=%#x u_eid=%#x is invalid\n", + pld->base.eid[0], pld->base.ueid[0]); + return -EINVAL; + } + + if (!ub_bus_instance_exist(pld->base.ueid[0])) { + dev_err(dev, "entity reg u_eid=%#x not exist\n", + pld->base.ueid[0]); + return -EINVAL; + } + + if (guid->bits.type != UB_TYPE_CONTROLLER) { + dev_err(dev, "entity reg guid type %u is invalid\n", + guid->bits.type); + return -EINVAL; + } + + if (!ers_valid) + return 0; + + ers = (struct entity_rs_info *)pld->ers; + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (ers[i].ss == 0) { + dev_err(dev, "entity reg ers[%d] size is 0\n", i); + return -EINVAL; + } + } + + return 0; +} + +static u8 ub_entity_reg_handle(struct ub_bus_controller *ubc, + struct pool_msg_pkt *pkt, bool ers_valid, + struct pool_fad **start_fad) +{ + struct entity_reg_msg_pld *pld = &pkt->reg; + struct pool_fad *fad; + struct ub_entity *uent; + int ret; + + uent = ub_get_ent_by_eid(pld->base.eid[0]); + if (uent) { + ub_entity_put(uent); + return UB_MSG_RSP_EXEC_EEXIST; + } + + ret = ub_entity_reg_check(ubc, pld, ers_valid); + if (ret) + return UB_MSG_RSP_EXEC_EINVAL; + + fad = kzalloc(sizeof(*fad), GFP_KERNEL); + if (!fad) + return UB_MSG_RSP_EXEC_ENOMEM; + + fad->ubc = ubc; + fad->ers_valid = ers_valid; + ub_fad_para_init(fad, pld); + + ret = ub_fad_attach(fad); + if (ret) { + dev_err(&ubc->dev, "fad idx[%u] add failed\n", fad->base.entity_idx); + kfree(fad); + return err_to_msg_rsp(ret); + } + + spin_lock(&ub_fad_lock); + list_add_tail(&fad->node, &ub_fad.node); + spin_unlock(&ub_fad_lock); + *start_fad = fad; + + return UB_MSG_RSP_SUCCESS; +} + +static u8 +ub_entity_rls_handle(struct ub_bus_controller *ubc, struct pool_msg_pkt *pkt) +{ + struct entity_rls_msg_pld *pld = &pkt->rls; + struct pool_fad *fad; + + fad = ub_get_fad(pld->eid[0]); + if (!fad) + return UB_MSG_RSP_EXEC_ENODEV; + + spin_lock(&ub_fad_lock); + list_del(&fad->node); + spin_unlock(&ub_fad_lock); + + if (fad->attach) { + ub_info(fad->uent, "fad detach, eid=%#x, reason=%#x\n", + fad->uent->eid, pld->reason); + ub_fad_detach(fad); + fad->attach = false; + } + + kfree(fad); + + return UB_MSG_RSP_SUCCESS; +} + +static void +ub_entity_rls_msg_handler(struct ub_bus_controller *ubc, void *msg, u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct msg_pkt_header *header = &pkt->header; + struct msg_info info = {}; + bool local; + u8 status; + int ret; + + if (p_len != MSG_ENTITY_RLS_SIZE) { + dev_err(&ubc->dev, "entity rls msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + status = ub_entity_rls_handle(ubc, pkt); + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, pkt, NULL, + (MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET)); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send entity rls rsp, ret=%d\n", ret); +} + +static void +ub_entity_reg_msg_handler(struct ub_bus_controller *ubc, void *msg, u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct msg_pkt_header *header = &pkt->header; + struct msg_info info = {}; + struct pool_fad *start_fad; + bool local, flag; + u32 feature = 0; + u8 status; + int ret; + + if (p_len != MSG_ENTITY_REG_SIZE) { + dev_err(&ubc->dev, "entity reg len err, len=%#x\n", p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + ret = ub_cfg_read_dword(ubc->uent, UB_CFG1_SUPPORT_FEATURE_L, &feature); + if (ret) { + dev_err(&ubc->dev, "entity reg feature read failed, ret=%d\n", ret); + status = UB_MSG_RSP_EXEC_ENOEXEC; + goto rsp; + } + + flag = !!(feature & UB_DECODER_JURIS); + status = ub_entity_reg_handle(ubc, pkt, flag, &start_fad); + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, pkt, NULL, + (MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET)); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send pool rsp msg, ret=%d\n", ret); + + if (status == UB_MSG_RSP_SUCCESS) + ub_start_ent(start_fad->uent); +} + +static void ub_cfg_cpl_notify_msg_rsp(struct ub_bus_controller *ubc, + struct msg_pkt_header *header) +{ + struct msg_info info = {}; + u32 tmp_eid, tmp_cna; + bool local; + int ret; + + tmp_eid = header->deid; + header->deid = eid_gen(header->seid_h, header->seid_l); + header->seid_h = seid_high(tmp_eid); + header->seid_l = seid_low(tmp_eid); + + tmp_cna = header->nth.scna; + header->nth.scna = header->nth.dcna; + header->nth.dcna = tmp_cna; + header->msgetah.type = MSG_RSP; + header->msgetah.plen = 0; + + local = (header->nth.scna == header->nth.dcna); + message_info_init(&info, local ? ubc->uent : NULL, header, NULL, + (MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET)); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send notify rsp failed, ret=%d\n", ret); +} + +int ub_fm_flush_ubc_info(struct ub_bus_controller *ubc) +{ + struct device *dev = &ubc->dev; + int ret = -ENOMEM; + u32 eid, fm_cna; + char *buf; + u16 upi; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) + goto out; + + ret = ub_query_ent_na(ubc->uent, buf); + if (ret) { + dev_err(dev, "update cluster ubc cna failed, ret=%d\n", ret); + goto free_buf; + } + + ret = ub_query_port_na(ubc->uent, buf); + if (ret) { + dev_err(dev, "update cluster ubc port cna failed, ret=%d\n", ret); + goto free_buf; + } + + ret = ub_cfg_read_word(ubc->uent, UB_UPI, &upi); + if (ret) { + dev_err(dev, "update cluster upi failed, ret=%d\n", ret); + goto free_buf; + } + + ubc->uent->upi = upi & UB_UPI_MASK; + dev_info(dev, "update cluster ubc upi to %#x\n", ubc->uent->upi); + + ret = ub_cfg_read_dword(ubc->uent, UB_EID_0, &eid); + if (ret) { + dev_err(dev, "update cluster ubc eid failed, ret=%d\n", ret); + goto free_buf; + } + + if (eid <= ubc_eid_end) { + dev_err(dev, "update cluster ubc wrong, eid=%#x\n", eid); + ret = -EINVAL; + goto free_buf; + } + + ubc->uent->eid = eid & UB_COMPACT_EID_MASK; + dev_info(dev, "update cluster ubc eid to %#x\n", ubc->uent->eid); + + ret = ub_cfg_read_dword(ubc->uent, UB_FM_CNA, &fm_cna); + if (ret) { + dev_err(dev, "read fm cna failed, ret=%d\n", ret); + goto free_buf; + } + + ubc->uent->fm_cna = fm_cna & UB_FM_CNA_MASK; + dev_info(dev, "update cluster ubc fm cna to %#x\n", ubc->uent->fm_cna); + +free_buf: + kfree(buf); +out: + return ret; +} + +static void ub_cfg_cpl_notify_handler(struct ub_bus_controller *ubc, void *msg, + u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct cfg_cpl_notify_pld *notify = &pkt->notify; + struct msg_pkt_header *header = &pkt->header; + u8 rsp_status = UB_MSG_RSP_SUCCESS; + int ret; + + if (p_len != MSG_CFG_CPL_NOTIFY_SIZE) { + dev_err(&ubc->dev, "notify msg len is wrong, len=%#x\n", p_len); + rsp_status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + ret = ub_fm_flush_ubc_info(ubc); + if (ret) { + rsp_status = err_to_msg_rsp(ret); + goto rsp; + } + + ret = ub_notify_bus_instance_handle(ubc, notify->flag, notify->guid, + notify->eid[0], ubc->uent->upi); + if (ret) { + dev_err(&ubc->dev, "handle notify bi failed, ret=%d\n", ret); + rsp_status = err_to_msg_rsp(ret); + goto rsp; + } + + if (!ub_entity_test_priv_flag(ubc->uent, UB_ENTITY_START)) { + ubc->uent->user_eid = notify->eid[0]; + ub_start_ent(ubc->uent); + } + +rsp: + header->msgetah.rsp_status = rsp_status; + ub_cfg_cpl_notify_msg_rsp(ubc, header); +} + +static void ub_pool_bi_handler(struct ub_bus_controller *ubc, void *msg, u16 p_len) +{ + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + u32 size = MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET; + struct msg_pkt_header *header = &pkt->header; + struct bi_create_pld *pld = &pkt->create; + u8 status = UB_MSG_RSP_SUCCESS; + struct device *dev = &ubc->dev; + struct msg_info info = {}; + bool local; + int ret; + + if (header->msgetah.sub_msg_code == UB_BI_CREATE) { + if (p_len != MSG_BI_CREATE_SIZE) { + dev_err(dev, "bi create msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + ret = ub_msg_bus_instance_create(ubc, pld->guid, pld->eid[0], + pld->upi, EID_NONE); + } else { + if (p_len != MSG_BI_DESTROY_SIZE) { + dev_err(dev, "bi destroy msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + ret = ub_msg_bus_instance_destroy(ubc, pld->guid); + } + + if (ret) + status = UB_MSG_RSP_EXEC_ENOEXEC; + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, pkt, pkt, size); + + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(dev, "send bi rsp msg, ret=%d\n", ret); +} + +static void ub_port_reset_notify_handler(struct ub_bus_controller *ubc, void *msg, + u16 p_len) +{ + u32 size = MSG_PKT_HEADER_SIZE << MSG_REQ_SIZE_OFFSET; + struct pool_msg_pkt *pkt = (struct pool_msg_pkt *)msg; + struct msg_pkt_header *header = &pkt->header; + struct port_reset_notify_pld *pld; + u8 status = UB_MSG_RSP_SUCCESS; + struct msg_info info = {}; + struct ub_port *port = NULL; + bool local; + int ret; + + pld = &pkt->port_reset; + if (p_len != MSG_PORT_RESET_SIZE) { + dev_err(&ubc->dev, + "ub fm port reset notify msg len is wrong, len=%#x\n", + p_len); + status = UB_MSG_RSP_CMD_LEN_ERR; + goto rsp; + } + + if (ub_port_reset_check(ubc->uent, pld->port_index)) { + status = UB_MSG_RSP_EXEC_ENOEXEC; + goto rsp; + } + + port = ubc->uent->ports + pld->port_index; + if (port->shareable && port->domain_boundary) { + if (pld->type == RESET_PREPARE) + ub_notify_share_port(port, UB_PORT_EVENT_RESET_PREPARE); + else if (pld->type == RESET_DONE) + ub_notify_share_port(port, UB_PORT_EVENT_RESET_DONE); + } + +rsp: + local = ub_rsp_msg_init(header, status, 0); + message_info_init(&info, local ? ubc->uent : NULL, header, NULL, size); + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, + "send ub fm port reset notify error, ret=%d\n", ret); +} + +static rx_msg_handler_t pool_rx_msg_handler[UB_SUB_MSG_CODE_NUM] = { + ub_entity_reg_msg_handler, + ub_entity_rls_msg_handler, + ub_pool_bi_handler, + ub_pool_bi_handler, + ub_cfg_cpl_notify_handler, + ub_port_reset_notify_handler, +}; + +void ub_pool_rx_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + u8 sub_msg_code = header->msgetah.sub_msg_code; + rx_msg_handler_t handler; + + handler = pool_rx_msg_handler[sub_msg_code]; + if (handler) + handler(ubc, pkt, len); + else + dev_err(&ubc->dev, "pool sub msg code not support, code=%#x\n", + sub_msg_code); +} diff --git a/drivers/ub/ubus/pool.h b/drivers/ub/ubus/pool.h new file mode 100644 index 0000000000000000000000000000000000000000..2a593bcb0410397f8e5e0197b7000e2d8347d7fb --- /dev/null +++ b/drivers/ub/ubus/pool.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __POOL_H__ +#define __POOL_H__ + +#include "msg.h" + +struct entity_base_info { + /* DW0 */ + u32 entity_idx : 16; + u32 upi : 15; + u32 rsvd0 : 1; + /* DW1~DW4 */ + u32 eid[4]; + /* DW5~DW8 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW9 */ + u32 cna : 24; + u32 rsvd2 : 8; + /* DW10~DW13 */ + u32 ueid[4]; +}; + +struct entity_rs_info { + u32 ss; + u32 sa_l; + u32 sa_h; +}; + +struct pool_fad { + struct entity_base_info base; + struct entity_rs_info ers[MAX_UB_RES_NUM]; + + bool attach; + bool ers_valid; + struct ub_entity *uent; + struct list_head node; + struct ub_bus_controller *ubc; +}; + +struct entity_reg_msg_pld { + /* DW0~DW13 */ + struct entity_base_info base; + /* DW14~DW22 */ + struct entity_rs_info ers[MAX_UB_RES_NUM]; +}; +#define ENTITY_BASE_PLD_SIZE 56 +#define ENTITY_RS_PLD_SIZE 36 +#define ENTITY_REG_PLD_SIZE (ENTITY_BASE_PLD_SIZE + ENTITY_RS_PLD_SIZE) + +struct entity_rls_msg_pld { + /* DW0~DW3 */ + u32 eid[4]; + /* DW4 */ + u32 reason : 8; + u32 rsvd1 : 24; +}; +#define ENTITY_RLS_PLD_SIZE 20 + +struct port_reset_notify_pld { + u32 type : 1; + u32 rsvd0 : 15; + u32 port_index : 16; +}; +#define PORT_RESET_NOTIFY_PLD_SIZE 4 +struct msg_pkt_header; +struct ub_entity *ub_get_fad_ent_by_eid(unsigned int eid); +bool ub_rsp_msg_init(struct msg_pkt_header *header, u8 status, u32 plen); +void ub_pool_rx_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +#endif /* __POOL_H__ */ diff --git a/drivers/ub/ubus/port.c b/drivers/ub/ubus/port.c new file mode 100644 index 0000000000000000000000000000000000000000..717a5507bd62071b83f870056eb5da8e48088d71 --- /dev/null +++ b/drivers/ub/ubus/port.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus port: " fmt + +#include + +#include "ubus.h" +#include "reset.h" +#include "link.h" +#include "port.h" + +struct ub_port_attribute { + struct attribute attr; + ssize_t (*show)(struct ub_port *port, char *buf); + ssize_t (*store)(struct ub_port *port, const char *buf, size_t count); +}; + +#define to_ub_port(o) container_of(o, struct ub_port, kobj) +#define to_ub_port_attr(a) container_of(a, struct ub_port_attribute, attr) + +#define UB_PORT_ATTR_RO(field) \ + static struct ub_port_attribute ub_port_attr_##field = __ATTR_RO(field) + +#define UB_PORT_ATTR_RW(field) \ + static struct ub_port_attribute ub_port_attr_##field = __ATTR_RW(field) + +#define UB_PORT_ATTR_WO(field) \ + static struct ub_port_attribute ub_port_attr_##field = __ATTR_WO(field) + +int ub_port_read_byte(struct ub_port *port, u32 pos, u8 *val) +{ + u64 base = UB_PORT_SLICE_START + port->index * UB_PORT_SLICE_SIZE; + + return ub_cfg_read_byte(port->uent, base + pos, val); +} + +int ub_port_read_word(struct ub_port *port, u32 pos, u16 *val) +{ + u64 base = UB_PORT_SLICE_START + port->index * UB_PORT_SLICE_SIZE; + + return ub_cfg_read_word(port->uent, base + pos, val); +} + +int ub_port_read_dword(struct ub_port *port, u32 pos, u32 *val) +{ + u64 base = UB_PORT_SLICE_START + port->index * UB_PORT_SLICE_SIZE; + + return ub_cfg_read_dword(port->uent, base + pos, val); +} + +int ub_port_write_byte(struct ub_port *port, u32 pos, u8 val) +{ + u64 base = UB_PORT_SLICE_START + port->index * UB_PORT_SLICE_SIZE; + + return ub_cfg_write_byte(port->uent, base + pos, val); +} + +int ub_port_write_word(struct ub_port *port, u32 pos, u16 val) +{ + u64 base = UB_PORT_SLICE_START + port->index * UB_PORT_SLICE_SIZE; + + return ub_cfg_write_word(port->uent, base + pos, val); +} + +int ub_port_write_dword(struct ub_port *port, u32 pos, u32 val) +{ + u64 base = UB_PORT_SLICE_START + port->index * UB_PORT_SLICE_SIZE; + + return ub_cfg_write_dword(port->uent, base + pos, val); +} + +static ssize_t cna_show(struct ub_port *port, char *buf) +{ + return sysfs_emit(buf, "%#06x\n", port->cna); +} +UB_PORT_ATTR_RO(cna); + +static ssize_t boundary_show(struct ub_port *port, char *buf) +{ + return sysfs_emit(buf, "%#01x\n", port->domain_boundary); +} +UB_PORT_ATTR_RO(boundary); + +static ssize_t linkup_show(struct ub_port *port, char *buf) +{ + u8 val; + + if (port->type == VIRTUAL) + return sysfs_emit(buf, "Virtual port don't support\n"); + + if (ub_port_read_byte(port, UB_PORT_PHYSICAL_PORT_LINK_STATUS, &val)) { + ub_err(port->uent, "get port link cap fail\n"); + return -EIO; + } + + return sysfs_emit(buf, "%u\n", val & UB_PORT_LINK_STATE); +} +UB_PORT_ATTR_RO(linkup); + +static ssize_t neighbor_port_idx_show(struct ub_port *port, char *buf) +{ + if (!port->r_uent) + return sysfs_emit(buf, "No Neighbor\n"); + return sysfs_emit(buf, "%u\n", port->r_index); +} +UB_PORT_ATTR_RO(neighbor_port_idx); + +static ssize_t neighbor_guid_show(struct ub_port *port, char *buf) +{ + struct ub_guid *guid; + int count; + + if (!port->r_uent && guid_is_null(&port->r_guid)) + return sysfs_emit(buf, "No Neighbor\n"); + + guid = port->r_uent ? &port->r_uent->guid : + (struct ub_guid *)&port->r_guid; + + count = ub_show_guid(guid, buf); + + return count + sysfs_emit_at(buf, count, "\n"); +} +UB_PORT_ATTR_RO(neighbor_guid); + +static ssize_t neighbor_show(struct ub_port *port, char *buf) +{ + if (!port->r_uent) + return sysfs_emit(buf, "No Neighbor\n"); + + return sysfs_emit(buf, "%05x\n", port->r_uent->uent_num); +} +UB_PORT_ATTR_RO(neighbor); + +static ssize_t asy_link_width_show(struct ub_port *port, char *buf) +{ + u8 val; + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CAP, &val)) { + ub_err(port->uent, "get port cap15 cap fail\n"); + return -EIO; + } + + if (val & ASY_LINK_WIDTH_MASK) + return sysfs_emit(buf, "Support\n"); + else + return sysfs_emit(buf, "Not Support\n"); +} +UB_PORT_ATTR_RO(asy_link_width); + +static ssize_t port_reset_store(struct ub_port *port, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + + ret = kstrtoul(buf, 0, &val); + if (ret || (val != 1)) { + ub_err(port->uent, "Invalid val for port reset\n"); + return -EINVAL; + } + + if (port->uent->ubc->cluster) { + ub_err(port->uent, "Port reset is not supported by sysfs in cluster mode\n"); + return -EINVAL; + } + + ret = ub_port_reset_function(port); + if (ret < 0) + return ret; + + return count; +} +UB_PORT_ATTR_WO(port_reset); + +static ssize_t glb_qdlws_show(struct ub_port *port, char *buf) +{ + u8 val; + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CTRL, &val)) { + ub_err(port->uent, "get global qdlws fail\n"); + return -EIO; + } + + return sysfs_emit(buf, "%s\n", str_enable_disable(val & GLB_QDLWS_MASK)); +} + +static ssize_t glb_qdlws_store(struct ub_port *port, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + u8 cur; + + ret = kstrtoul(buf, 0, &val); + if (ret || (val != 0 && val != 1)) { + ub_err(port->uent, "Invalid val for global qdlws\n"); + return -EINVAL; + } + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CTRL, &cur)) { + ub_err(port->uent, "get global qdlws fail\n"); + return -EIO; + } + + if (val) + cur = cur | GLB_QDLWS_ENABLE_MASK; + else + cur = cur & GLB_QDLWS_DISABLE_MASK; + + if (ub_port_write_byte(port, PORT_CAP15_QDLWS_CTRL, cur)) { + ub_err(port->uent, "set global qdlws fail\n"); + return -EIO; + } + + return count; +} +UB_PORT_ATTR_RW(glb_qdlws); + +static ssize_t tx_qdlws_show(struct ub_port *port, char *buf) +{ + u8 val; + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CTRL, &val)) { + ub_err(port->uent, "get TX qdlws fail\n"); + return -EIO; + } + + return sysfs_emit(buf, "%s\n", str_enable_disable(val & TX_QDLWS_MASK)); +} + +static ssize_t tx_qdlws_store(struct ub_port *port, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + u8 cur; + + ret = kstrtoul(buf, 0, &val); + if (ret || (val != 0 && val != 1)) { + ub_err(port->uent, "Invalid val for TX qdlws\n"); + return -EINVAL; + } + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CTRL, &cur)) { + ub_err(port->uent, "get value for TX qdlws fail\n"); + return -EIO; + } + + if (val) + cur = cur | TX_QDLWS_ENABLE_MASK; + else + cur = cur & TX_QDLWS_DISABLE_MASK; + + if (ub_port_write_byte(port, PORT_CAP15_QDLWS_CTRL, cur)) { + ub_err(port->uent, "set TX qdlws fail\n"); + return -EIO; + } + + return count; +} +UB_PORT_ATTR_RW(tx_qdlws); + +static ssize_t rx_qdlws_show(struct ub_port *port, char *buf) +{ + u8 val; + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CTRL, &val)) { + ub_err(port->uent, "get RX qdlws fail\n"); + return -EIO; + } + + return sysfs_emit(buf, "%s\n", str_enable_disable(val & RX_QDLWS_MASK)); +} + +static ssize_t rx_qdlws_store(struct ub_port *port, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + u8 cur; + + ret = kstrtoul(buf, 0, &val); + if (ret || (val != 0 && val != 1)) { + ub_err(port->uent, "Invalid val for RX qdlws\n"); + return -EINVAL; + } + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_CTRL, &cur)) { + ub_err(port->uent, "get value for RX qdlws fail\n"); + return -EIO; + } + + if (val) + cur = cur | RX_QDLWS_ENABLE_MASK; + else + cur = cur & RX_QDLWS_DISABLE_MASK; + + if (ub_port_write_byte(port, PORT_CAP15_QDLWS_CTRL, cur)) { + ub_err(port->uent, "set RX qdlws fail\n"); + return -EIO; + } + + return count; +} +UB_PORT_ATTR_RW(rx_qdlws); + +static const char * const status[] = { "IDLE", "NAK", "In progress", "Timeout", + "Successful Done" }; + +static ssize_t qdlws_exec_state_show(struct ub_port *port, char *buf) +{ + u8 val; + + if (ub_port_read_byte(port, PORT_CAP15_QDLWS_STATE, &val)) { + ub_err(port->uent, "get qdlws exec state fail\n"); + return -EIO; + } + + val = val & QDLWS_EXEC_STATUS_MASK; + if (val > QDLWS_EXEC_STATUS_MAX) { + ub_err(port->uent, "get error state, value[%u]\n", val); + return sysfs_emit(buf, "Not support state\n"); + } + + return sysfs_emit(buf, "%s\n", status[val]); +} +UB_PORT_ATTR_RO(qdlws_exec_state); + +static struct attribute *ub_port_default_attrs[] = { + &ub_port_attr_cna.attr, + &ub_port_attr_boundary.attr, + &ub_port_attr_linkup.attr, + /* neighbor info */ + &ub_port_attr_neighbor_port_idx.attr, + &ub_port_attr_neighbor_guid.attr, + &ub_port_attr_neighbor.attr, + &ub_port_attr_port_reset.attr, + NULL +}; + +static const struct attribute_group ub_port_default_group = { + .attrs = ub_port_default_attrs, +}; + +static struct attribute *ub_port_qdlws_attrs[] = { + &ub_port_attr_asy_link_width.attr, + &ub_port_attr_glb_qdlws.attr, + &ub_port_attr_tx_qdlws.attr, + &ub_port_attr_rx_qdlws.attr, + &ub_port_attr_qdlws_exec_state.attr, + NULL +}; + +static umode_t ub_port_qdlws_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct ub_port *port = to_ub_port(kobj); + struct ub_entity *uent = port->uent; + + if (port->type == VIRTUAL) + return 0; + + if (is_ibus_controller(uent) && uent->ubc->cluster) + return 0; + + if (test_bit(UB_PORT_CAP15_QDLWS, port->cap_map)) + return a->mode; + + return 0; +} + +static const struct attribute_group ub_port_qdlws_group = { + .is_visible = ub_port_qdlws_is_visible, + .attrs = ub_port_qdlws_attrs, +}; + +static const struct attribute_group *ub_port_groups[] = { + &ub_port_default_group, + &ub_port_qdlws_group, + NULL +}; + +static ssize_t ub_port_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct ub_port_attribute *attribute = to_ub_port_attr(attr); + struct ub_port *port = to_ub_port(kobj); + + if (!attribute->show) + return -EIO; + + return attribute->show(port, buf); +} + +static ssize_t ub_port_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct ub_port_attribute *attribute = to_ub_port_attr(attr); + struct ub_port *port = to_ub_port(kobj); + + if (!attribute->store) + return -EIO; + + return attribute->store(port, buf, count); +} + +static const struct sysfs_ops ub_port_sysfs_ops = { + .show = ub_port_attr_show, + .store = ub_port_attr_store, +}; + +static const struct kobj_type ub_port_ktype = { + .sysfs_ops = &ub_port_sysfs_ops, + .default_groups = ub_port_groups, +}; + +void ub_port_disconnect(struct ub_port *port) +{ + struct ub_port *r_port; + + if (!port || !port->r_uent) + return; + + r_port = port->r_uent->ports + port->r_index; + r_port->r_uent = NULL; + r_port->r_guid = guid_null; + + port->r_uent = NULL; + port->r_guid = guid_null; +} + +/* connect two ports, caller should make sure that the ports match */ +void ub_port_connect(struct ub_port *port, struct ub_port *r_port) +{ + if (!port || !r_port) + return; + + port->r_index = r_port->index; + port->r_uent = r_port->uent; + r_port->r_index = port->index; + r_port->r_uent = port->uent; +} + +bool ub_check_and_connect(struct ub_port *port, struct ub_entity *r_uent) +{ + struct ub_port *r_port; + + if (!port || !r_uent) + return false; + + if (port->r_index >= r_uent->port_nums) { + pr_err("port%u should connect to port%u, but remote device has only %u ports\n", + port->index, port->r_index, r_uent->port_nums); + return false; + } + + r_port = r_uent->ports + port->r_index; + + if (r_port->r_uent) { + pr_err("port%u should connect to port%u, which is already connected\n", + port->index, port->r_index); + return false; + } + + ub_port_connect(port, r_port); + + return true; +} + +static int ub_port_config(struct ub_port *port) +{ + u32 map[UB_PORT_CAP_NUM / SZ_32]; + int ret, i; + + /* virtual port doesn't have bitmap */ + if (port->type == VIRTUAL) + return 0; + + for (i = 0; i < UB_PORT_CAP_NUM / SZ_32; i++) { + ret = ub_port_read_dword( + port, UB_CFG0_PORT_BITMAP + i * sizeof(u32), &map[i]); + if (ret) { + ub_err(port->uent, + "failed to read port%u bitmap %d with %d\n", + port->index, i, ret); + return ret; + } + } + + memcpy((void *)port->cap_map, (void *)map, sizeof(map)); + return 0; +} + +int ub_ports_add(struct ub_entity *uent) +{ + struct ub_port *port; + int ret; + + if (!uent) + return -EINVAL; + + for_each_uent_port(port, uent) { + ret = ub_port_config(port); + if (ret) { + ub_err(uent, + "config port%u failed, stop adding ports\n", + port->index); + return ret; + } + + ret = kobject_add(&port->kobj, &uent->dev.kobj, "port%u", + port->index); + if (ret) { + ub_warn(uent, "cannot add port%u, stop adding ports\n", + port->index); + return ret; + } + } + + return 0; +} + +void ub_ports_del(struct ub_entity *uent) +{ + struct ub_port *port; + + if (!uent) + return; + + for_each_uent_port(port, uent) + kobject_put(&port->kobj); +} + +static void ub_port_init(struct ub_entity *uent, struct ub_port *port) +{ + port->uent = uent; + port->type = PHYSICAL; + port->cna = 0; + port->r_uent = NULL; + port->link_state = LINK_STATE_NORMAL; + port->r_index = 0; + port->r_guid = guid_null; + bitmap_zero(port->cna_maps, UB_MAX_CNA_NUM); + bitmap_zero(port->cap_map, UB_PORT_CAP_NUM); + kobject_init(&port->kobj, &ub_port_ktype); +} + +int ub_ports_setup(struct ub_entity *uent) +{ + struct ub_port *port; + + if (!uent || !uent->port_nums) + return -EINVAL; + + if (uent->ports) + return 0; + + uent->ports = kvcalloc(uent->port_nums, sizeof(*port), GFP_KERNEL); + if (!uent->ports) + return -ENOMEM; + + for_each_uent_port(port, uent) { + port->index = port - uent->ports; + ub_port_init(uent, port); + } + + return 0; +} + +void ub_ports_unset(struct ub_entity *uent) +{ + if (!uent) + return; + + kvfree(uent->ports); + uent->ports = NULL; + pr_debug("port release\n"); +} + +static LIST_HEAD(ub_share_port_notify_list); +static DECLARE_RWSEM(ub_share_port_notify_list_rwsem); + +struct ub_share_port_notify_node { + struct ub_entity *parent; + struct ub_entity *entity; + u16 port_id; + struct ub_share_port_ops *ops; + struct list_head node; +}; + +int ub_register_share_port(struct ub_entity *entity, u16 port_id, + struct ub_share_port_ops *ops) +{ + struct ub_share_port_notify_node *notify_node; + struct ub_entity *parent; + struct ub_port *port; + + if (unlikely(!entity || !ops)) + return -EINVAL; + + if (!is_idev(entity) && !is_ibus_controller(entity)) { + ub_err(entity, + "don't support device with type %u register share port\n", + uent_type(entity)); + return -EINVAL; + } + + /* get primary entity first */ + parent = entity; + if (is_idev(parent)) { + while (!is_primary(parent)) + parent = parent->pue; + + /* check parent is controller */ + parent = to_ub_entity(parent->dev.parent); + if (!is_ibus_controller(parent)) { + ub_err(entity, "don't support register share port at non-controller device with type %u\n", + uent_type(parent)); + return -EINVAL; + } + } + + if (port_id >= parent->port_nums) { + ub_err(parent, "port id %u exceeds port num %u\n", port_id, + parent->port_nums); + return -EINVAL; + } + + port = parent->ports + port_id; + if (is_idev(entity) && !port->shareable) { + ub_err(parent, "port%u isn't shareable\n", port_id); + return -EINVAL; + } + + notify_node = kzalloc(sizeof(*notify_node), GFP_KERNEL); + if (!notify_node) + return -ENOMEM; + + notify_node->parent = parent; + notify_node->entity = entity; + notify_node->port_id = port_id; + notify_node->ops = ops; + INIT_LIST_HEAD(¬ify_node->node); + + down_write(&ub_share_port_notify_list_rwsem); + list_add_tail(¬ify_node->node, &ub_share_port_notify_list); + up_write(&ub_share_port_notify_list_rwsem); + + ub_info(entity, "register share port at %u success\n", port_id); + return 0; +} +EXPORT_SYMBOL_GPL(ub_register_share_port); + +void ub_unregister_share_port(struct ub_entity *entity, u16 port_id, + struct ub_share_port_ops *ops) +{ + struct ub_share_port_notify_node *notify_node; + + if (unlikely(!entity)) + return; + + down_write(&ub_share_port_notify_list_rwsem); + + list_for_each_entry(notify_node, &ub_share_port_notify_list, node) { + if (notify_node->entity != entity || + notify_node->port_id != port_id || notify_node->ops != ops) + continue; + + list_del(¬ify_node->node); + kfree(notify_node); + ub_info(entity, "unregister share port at %u success\n", port_id); + goto unlock; + } + + ub_err(entity, "share port %u isn't registered, unregister failed\n", + port_id); +unlock: + up_write(&ub_share_port_notify_list_rwsem); +} +EXPORT_SYMBOL_GPL(ub_unregister_share_port); + +void ub_notify_share_port(struct ub_port *port, + enum ub_port_event type) +{ + struct ub_share_port_notify_node *notify_node; + struct ub_share_port_ops *ops; + struct ub_entity *uent; + + if (!port || type > UB_PORT_EVENT_RESET_DONE) + return; + + uent = port->uent; + down_read(&ub_share_port_notify_list_rwsem); + list_for_each_entry(notify_node, &ub_share_port_notify_list, node) { + if (notify_node->parent != uent || + notify_node->port_id != port->index) + continue; + + ops = notify_node->ops; + if (ops->event_notify) + ops->event_notify(notify_node->entity, + notify_node->port_id, type); + } + up_read(&ub_share_port_notify_list_rwsem); +} +EXPORT_SYMBOL_GPL(ub_notify_share_port); + +bool ub_port_check_link_up(struct ub_port *port) +{ + u8 val; + + if (!port) + return false; + + if (ub_port_read_byte(port, UB_PORT_PHYSICAL_PORT_LINK_STATUS, &val)) + return false; + + return !!(val & UB_PORT_LINK_STATE); +} diff --git a/drivers/ub/ubus/port.h b/drivers/ub/ubus/port.h new file mode 100644 index 0000000000000000000000000000000000000000..2c07053fc8306fde49183481d5dc24f32d380b77 --- /dev/null +++ b/drivers/ub/ubus/port.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __PORT_H__ +#define __PORT_H__ + +#define for_each_uent_port(p, d) \ + for ((p) = (d)->ports; ((p) - (d)->ports) < (d)->port_nums; (p)++) + +enum ub_port_reset_notify_type { + RESET_PREPARE, + RESET_DONE +}; + +struct ub_port; +struct ub_entity; +void ub_port_disconnect(struct ub_port *port); +void ub_port_connect(struct ub_port *port, struct ub_port *r_port); +bool ub_check_and_connect(struct ub_port *port, struct ub_entity *r_uent); +int ub_ports_add(struct ub_entity *uent); +void ub_ports_del(struct ub_entity *uent); +int ub_ports_setup(struct ub_entity *uent); +void ub_ports_unset(struct ub_entity *uent); +void ub_notify_share_port(struct ub_port *port, + enum ub_port_event type); + +int ub_port_read_byte(struct ub_port *port, u32 pos, u8 *val); +int ub_port_write_dword(struct ub_port *port, u32 pos, u32 val); +bool ub_port_check_link_up(struct ub_port *port); + +#endif /* __PORT_H__ */ diff --git a/drivers/ub/ubus/ras.c b/drivers/ub/ubus/ras.c new file mode 100644 index 0000000000000000000000000000000000000000..a6553a661ac5ff0076cd203dc46efa0a526c453e --- /dev/null +++ b/drivers/ub/ubus/ras.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include + +static ub_ras_recover_func_t ub_ras_recover_func; +static DEFINE_SPINLOCK(ubus_ghes_lock); + +void ub_ras_register_recover_func(ub_ras_recover_func_t func) + +{ + spin_lock(&ubus_ghes_lock); + ub_ras_recover_func = func; + spin_unlock(&ubus_ghes_lock); +} +EXPORT_SYMBOL_GPL(ub_ras_register_recover_func); + +ub_ras_recover_func_t ub_ras_get_recover_func(void) +{ + ub_ras_recover_func_t tmp_func; + + spin_lock(&ubus_ghes_lock); + tmp_func = ub_ras_recover_func; + spin_unlock(&ubus_ghes_lock); + + return tmp_func; +} +EXPORT_SYMBOL_GPL(ub_ras_get_recover_func); diff --git a/drivers/ub/ubus/reset.c b/drivers/ub/ubus/reset.c new file mode 100644 index 0000000000000000000000000000000000000000..e1ecb04ebe31c2cd0e5238b1126f4367f5543ef3 --- /dev/null +++ b/drivers/ub/ubus/reset.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus reset: " fmt + +#include + +#include "ubus.h" +#include "port.h" +#include "route.h" +#include "ubus_controller.h" +#include "ubus_config.h" +#include "reset.h" + +enum elr_type { + ELR_PREPARE = 0, + ELR_DONE = 1, +}; + +enum SAVED_CFG_SPACE_NUM { + DEV_TOKEN_ID = 0, +}; + +static u32 saved_cfg_offset[] = { + [DEV_TOKEN_ID] = UB_CFG1_BASIC + 0xb4, +}; + +/** + * ub_elr - Initiate an UB entity level reset + * @dev: UB entity to reset + */ +static int ub_elr(struct ub_entity *dev) +{ + u8 command; + u8 val = 0; + int ret; + + /* enable ELR */ + command = 0x01; + ret = ub_cfg_write_byte(dev, UB_ELR, command); + if (ret) { + ub_err(dev, "dev elr failed, write byte error, ret=%d", ret); + return ret; + } + + /* + * ub entity must complete an ELR within 100ms, + * but may silently discard requests while the ELR is in + * progress. Wait 100ms before trying to access the device. + */ + msleep(100); + + ret = ub_cfg_read_byte(dev, UB_ELR_DONE, &val); + if (ret || !val) { + ub_err(dev, "dev elr failed, ret=%d, ELR_DONE=%u\n", ret, val); + return -EINVAL; + } + + ub_info(dev, "dev elr success\n"); + + return 0; +} + +/** + * ub_device_reset - Initiate a UB entity reset + * @ent: UB entity. + */ +int ub_device_reset(struct ub_entity *ent) +{ + u16 command; + int ret; + + if (!ent) { + pr_err("device which will be reset is NULL\n"); + return -EINVAL; + } + + if (is_ibus_controller(ent)) { + ub_warn(ent, "ub bus controller do not support reset.\n"); + return -EINVAL; + } + + /* device reset only support Entity0 */ + if (ent->entity_idx) { + ub_err(ent, "device is not entity0, entity_idx=%u\n", ent->entity_idx); + return -EINVAL; + } + + device_lock(&ent->dev); + + /* enable device reset */ + command = 0x01; + ret = ub_send_cfg(ent, (u8)sizeof(u16), UB_ENTITY_RST, (u32 *)&command); + if (ret) { + ub_err(ent, "device reset failed, ret=%d\n", ret); + device_unlock(&ent->dev); + return -EIO; + } + + ub_entity_assign_priv_flag(ent, UB_ENTITY_DISCONNECTED, true); + + device_unlock(&ent->dev); + ub_info(ent, "device reset success\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(ub_device_reset); + +static void ub_save_token_state(struct ub_entity *dev) +{ + int ret; + int i; + + for (i = DEV_TOKEN_ID; i <= DEV_TOKEN_ID; i++) { + ret = ub_cfg_read_dword(dev, saved_cfg_offset[i], + &dev->saved_config_space[i]); + if (ret) { + ub_err(dev, "ub cfg read dword failed, save cfg offset: %#x.\n", + saved_cfg_offset[i]); + continue; + } + ub_info(dev, "saving config space at address %#x (reading %#x)\n", + saved_cfg_offset[i], dev->saved_config_space[i]); + } +} + +static int ub_save_state(struct ub_entity *dev) +{ + const struct ub_error_handlers *err_handler = + dev->driver ? dev->driver->err_handler : NULL; + + if (err_handler && err_handler->ub_reset_prepare) + err_handler->ub_reset_prepare(dev); + + ub_save_token_state(dev); + + dev->state_saved = true; + + return 0; +} + +static void ub_restore_config_dword(struct ub_entity *dev, u32 pos, u32 saved_val) +{ + int retry = 10; + u32 val; + int ret; + + ret = ub_cfg_read_dword(dev, pos, &val); + if (ret || val == saved_val) + return; + while (retry-- > 0) { + ub_info(dev, "restoring config space at address %#x (was %#x, writing %#x)\n", + pos, val, saved_val); + ub_cfg_write_dword(dev, pos, saved_val); + + if (ub_cfg_read_dword(dev, pos, &val)) + continue; + + if (val == saved_val) + return; + +#define RESTORE_RETRY_SLEEP_MS 1 + msleep(RESTORE_RETRY_SLEEP_MS); + } +} + +static void ub_restore_state(struct ub_entity *dev) +{ + const struct ub_error_handlers *err_handler = + dev->driver ? dev->driver->err_handler : NULL; + int i; + + if (!dev->state_saved) + return; + + for (i = DEV_TOKEN_ID; i <= DEV_TOKEN_ID; i++) { + ub_restore_config_dword(dev, saved_cfg_offset[i], + dev->saved_config_space[i]); + } + + dev->state_saved = false; + + if (err_handler && err_handler->ub_reset_done) + err_handler->ub_reset_done(dev); +} + +static int ub_reset_check(struct ub_entity *dev) +{ + if (is_ibus_controller(dev)) { + ub_err(dev, "UB Bus Controller does not support ELR!\n"); + return -EINVAL; + } + + if (!dev->reset_fn) + return -ENOTTY; + + return 0; +} + +int ub_reset_entity(struct ub_entity *dev) +{ + int ret, rc; + + if (!dev) { + pr_err("device is NULL\n"); + return -EINVAL; + } + + rc = ub_reset_check(dev); + if (rc) + return rc; + + if (!device_trylock(&dev->dev)) + return -EBUSY; + + ret = ub_save_state(dev); + if (ret) { + device_unlock(&dev->dev); + return ret; + } + + ub_entity_enable(dev, 0); + + rc = ub_elr(dev); + + ub_restore_state(dev); + + device_unlock(&dev->dev); + + return rc; +} +EXPORT_SYMBOL_GPL(ub_reset_entity); + +int ub_port_reset_check(struct ub_entity *dev, int port_id) +{ + struct ub_port *port = NULL; + + if (!dev) + return -EINVAL; + + if (is_idev(dev)) { + ub_err(dev, "IDEV does not support port reset!\n"); + return -EINVAL; + } + + if (port_id >= dev->port_nums) { + ub_err(dev, "Can't reset port because port(%d) is over port_nums(%u)!\n", + port_id, dev->port_nums); + return -EINVAL; + } + + port = dev->ports + port_id; + + if (port->type == VIRTUAL) { + ub_err(dev, "vport reset is not supported now!\n"); + return -EINVAL; + } + + return 0; +} + +static bool ub_wait_port_complete(struct ub_port *port) +{ + u64 timeout = 0; + +#define TOTAL_WAIT_CNT 150 /* wait 15s for port reset */ +#define WAIT_TIME 100 + + do { + if (port->link_state == LINK_STATE_DONE) + return true; + + msleep(WAIT_TIME); + timeout++; + } while (timeout < TOTAL_WAIT_CNT); + + return false; +} + +int ub_port_reset(struct ub_entity *dev, int port_id) +{ + struct ub_port *port = NULL; + int ret; + + if (ub_port_reset_check(dev, port_id)) + return -EINVAL; + + device_lock(&dev->dev); + port = dev->ports + port_id; + if (port->link_state != LINK_STATE_NORMAL) { + ub_err(dev, "port reset is not complete last time!\n"); + device_unlock(&dev->dev); + return -EINVAL; + } + + ub_notify_share_port(port, UB_PORT_EVENT_RESET_PREPARE); + + /* enable port reset */ + ret = ub_port_write_dword(port, UB_PORT_RST, 0x01); + if (ret) { + ub_err(port->uent, "port reset failed, dev_eid=%u, port_id=%d, ret=%d\n", + dev->eid, port_id, ret); + device_unlock(&dev->dev); + return -EIO; + } + + port->link_state = LINK_STATE_RESETING; + + device_unlock(&dev->dev); + + if (ub_wait_port_complete(port)) { + ub_notify_share_port(port, UB_PORT_EVENT_RESET_DONE); + port->link_state = LINK_STATE_NORMAL; + ub_info(dev, "port(%d) reset success!\n", port_id); + return ret; + } + + port->link_state = LINK_STATE_NORMAL; + ub_err(dev, "port(%d) reset timeout!\n", port_id); + return -ETIME; +} +EXPORT_SYMBOL_GPL(ub_port_reset); + +int ub_port_reset_function(struct ub_port *port) +{ + return ub_port_reset(port->uent, port->index); +} diff --git a/drivers/ub/ubus/reset.h b/drivers/ub/ubus/reset.h new file mode 100644 index 0000000000000000000000000000000000000000..f8ab56198c0a2f2c4281f72639e9f0d7cf196466 --- /dev/null +++ b/drivers/ub/ubus/reset.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __RESET_H__ +#define __RESET_H__ + +struct ub_port; +struct ub_entity; + +int ub_port_reset_function(struct ub_port *port); +int ub_port_reset(struct ub_entity *dev, int port_id); +int ub_port_reset_check(struct ub_entity *dev, int port_id); + +#endif /* __RESET_H__ */ diff --git a/drivers/ub/ubus/resource.c b/drivers/ub/ubus/resource.c new file mode 100644 index 0000000000000000000000000000000000000000..258288ba584fcbeb15be616f7d28cafc570fad2d --- /dev/null +++ b/drivers/ub/ubus/resource.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus resource: " fmt + +#include +#include +#include + +#include "ubus.h" +#include "msg.h" +#include "decoder.h" +#include "resource.h" + +struct query_token_msg_pld_req { + u32 token_enable : 1; + u32 reserved : 31; +}; +#define QUERY_TOKEN_MSG_REQ_PLD_SIZE 4 +#define QUERY_TOKEN_MSG_REQ_SIZE 36 + +struct query_token_msg_pld_rsp { + u32 token_id : 20; + u32 reserved : 12; + u32 token_value; +}; +#define QUERY_TOKEN_MSG_RSP_SIZE 40 + +struct query_token_msg_pld { + union { + struct query_token_msg_pld_req req; + struct query_token_msg_pld_rsp rsp; + }; +}; + +struct query_token_msg_pkt { + struct msg_pkt_header header; + struct query_token_msg_pld pld; +}; + +static const struct vm_operations_struct ub_phys_vm_ops = { + .access = generic_access_phys, +}; + +int ub_mmap_resource_range(struct ub_entity *uent, unsigned long idx, + struct vm_area_struct *vma, int write_combine) +{ + resource_size_t size; + + size = ((ub_resource_len(uent, idx) - 1) >> PAGE_SHIFT) + 1; + if (vma->vm_pgoff + vma_pages(vma) > size) + return -EINVAL; + + if (write_combine) + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + else + vma->vm_page_prot = pgprot_device(vma->vm_page_prot); + + vma->vm_pgoff += (ub_resource_start(uent, idx) >> PAGE_SHIFT); + vma->vm_ops = &ub_phys_vm_ops; + + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +static int _ub_assign_resource(struct ub_entity *uent, int idx, + resource_size_t size, resource_size_t align) +{ + struct resource *res; + struct resource_entry *entry, *tmp; + struct resource *uent_res = &uent->zone[idx].res; + struct ub_bus_controller *ubc = uent->ubc; + int ret = -EINVAL; + + if (ubc == NULL) { + ub_err(uent, "ub assign resource failed, ubc is NULL.\n"); + return -EINVAL; + } + + resource_list_for_each_entry_safe(entry, tmp, &ubc->resources) { + res = entry->res; + + if (!(res->flags & IORESOURCE_MEM) || + !strstr(res->name, "UB_BUS_CTL")) + continue; + ret = allocate_resource(res, uent_res, size, 0, ~0, align, NULL, + NULL); + if (ret == 0) + return 0; + } + ub_err(uent, "ub assign resource failed, no res available, ret = %d\n", ret); + + return -ENOMEM; +} + +static int ub_assign_resource(struct ub_entity *uent, int idx) +{ + struct resource *res = &uent->zone[idx].res; + resource_size_t size; + resource_size_t page_size = PAGE_SIZE; + /* decoder mapped address unit 1M */ + resource_size_t align = page_size > SZ_1M ? page_size : SZ_1M; + int ret; + + res->name = ub_name(uent); + res->flags |= IORESOURCE_UNSET | IORESOURCE_SIZEALIGN; + size = uent->zone[idx].region.size; + ret = _ub_assign_resource(uent, idx, size, align); + if (ret < 0) { + ub_err(uent, "RESOURCE %d: failed to assign, size=%#llx\n", + idx, size); + return ret; + } + + res->flags &= ~IORESOURCE_UNSET; + res->flags &= ~IORESOURCE_STARTALIGN; + ub_info(uent, "RESOURCE %d: assigned %pR\n", idx, res); + + return 0; +} + +static void ub_release_resource(struct ub_entity *uent, int idx) +{ + int ret; + + if (uent->zone[idx].res.parent) { + ret = release_resource(&uent->zone[idx].res); + if (ret) + ub_err(uent, "resource release failed, ret=%d.\n", ret); + } +} + +static void __iomem *ub_iomap_range(struct ub_entity *uent, int res_id, + unsigned long offset, unsigned long maxlen, + bool is_wc) +{ + resource_size_t start = ub_resource_start(uent, res_id); + resource_size_t len = ub_resource_len(uent, res_id); + unsigned long flags = ub_resource_flags(uent, res_id); + + if (len <= offset || !start) + return NULL; + len -= offset; + start += offset; + if (maxlen && len > maxlen) + len = maxlen; + if (!(flags & IORESOURCE_MEM)) + return NULL; + + if (is_wc) + return ioremap_wc(start, len); + else + return ioremap(start, len); +} + +void __iomem *ub_iomap(struct ub_entity *uent, int res_id, unsigned long maxlen) +{ + if (res_id >= MAX_UB_RES_NUM || !uent) + return NULL; + + return ub_iomap_range(uent, res_id, 0, maxlen, false); +} +EXPORT_SYMBOL_GPL(ub_iomap); + +void __iomem *ub_iomap_wc(struct ub_entity *uent, int res_id, unsigned long maxlen) +{ + if (res_id >= MAX_UB_RES_NUM || !uent) + return NULL; + + return ub_iomap_range(uent, res_id, 0, maxlen, true); +} +EXPORT_SYMBOL_GPL(ub_iomap_wc); + +void ub_iounmap(void __iomem *addr) +{ + iounmap(addr); +} +EXPORT_SYMBOL_GPL(ub_iounmap); + +static int ub_read_ubba_reg(struct ub_entity *uent, size_t pg_size, int idx) +{ + u32 addr_l_reg[MAX_UB_RES_NUM] = { UB_ERS0_UBBA_L, UB_ERS1_UBBA_L, + UB_ERS2_UBBA_L }; + u32 addr_h_reg[MAX_UB_RES_NUM] = { UB_ERS0_UBBA_H, UB_ERS1_UBBA_H, + UB_ERS2_UBBA_H }; + u32 ss_reg[MAX_UB_RES_NUM] = { UB_ERS0_SS, UB_ERS1_SS, UB_ERS2_SS }; + u32 hpa_l = 0, hpa_h = 0, size = 0; + int ret; + + ret = ub_cfg_read_dword(uent, addr_l_reg[idx], &hpa_l); + ret |= ub_cfg_read_dword(uent, addr_h_reg[idx], &hpa_h); + ret |= ub_cfg_read_dword(uent, ss_reg[idx], &size); + if (ret) { + ub_err(uent, "read reg failed when request HPA resource\n"); + return -EINVAL; + } else if (size == 0) { + ub_err(uent, "size is 0 when request HPA resource\n"); + return -EINVAL; + } + + uent->zone[idx].res.start = ubba_gen(hpa_h, hpa_l); + uent->zone[idx].res.end = + uent->zone[idx].res.start + ((u64)size * pg_size - 1); + + uent->zone[idx].res.flags = IORESOURCE_MEM; + uent->zone[idx].res.name = ub_name(uent); + uent->zone[idx].ubba_used = 1; + + ub_info(uent, "mmio idx %d, %pR\n", idx, &uent->zone[idx].res); + return 0; +} + +static int ub_read_sa_reg(struct ub_entity *uent, size_t pg_size, int idx) +{ + u32 sa_l_reg[MAX_UB_RES_NUM] = { UB_ERS0_SA_L, UB_ERS1_SA_L, + UB_ERS2_SA_L }; + u32 sa_h_reg[MAX_UB_RES_NUM] = { UB_ERS0_SA_H, UB_ERS1_SA_H, + UB_ERS2_SA_H }; + u32 ss_reg[MAX_UB_RES_NUM] = { UB_ERS0_SS, UB_ERS1_SS, UB_ERS2_SS }; + u32 sa_l = 0, sa_h = 0, size = 0; + int ret; + + ret = ub_cfg_read_dword(uent, sa_l_reg[idx], &sa_l); + ret |= ub_cfg_read_dword(uent, sa_h_reg[idx], &sa_h); + ret |= ub_cfg_read_dword(uent, ss_reg[idx], &size); + if (ret) { + ub_err(uent, "read reg failed when request SA resource\n"); + return -EINVAL; + } else if (size == 0) { + ub_err(uent, "size is 0 when request SA resource\n"); + return -EINVAL; + } + + /* Shift left 32 bits to get upper-bit address */ + uent->zone[idx].region.start = ((u64)sa_h << 32) | sa_l; + uent->zone[idx].region.size = size * pg_size; + uent->zone[idx].res.flags = IORESOURCE_MEM; + uent->zone[idx].sa_used = 1; + + return 0; +} + +static int ub_entity_read_mmio_idx(struct ub_entity *uent, int idx) +{ + struct ub_entity *pue; + size_t pg_size = 0; + + /* ue to its mue, mue to Entity0, Entity0 use itself */ + pue = uent->pue; + if (pue->entity_idx) /* to ensure Entity0 uent */ + pue = pue->pue; + + (void)ub_cfg_read_byte(uent, UB_SYS_PGS, (u8 *)&pg_size); + if ((u8)pg_size & UB_SYS_PGS_SIZE) + pg_size = SZ_64K; + else + pg_size = SZ_4K; + + if (pue->zone[idx].ubba_used) + return ub_read_ubba_reg(uent, pg_size, idx); + + if (pue->zone[idx].sa_used) + return ub_read_sa_reg(uent, pg_size, idx); + + return -EPERM; +} + +int ub_insert_resource(struct ub_entity *dev, int idx) +{ + struct resource *res, *root = NULL; + int ret; + + if (idx >= MAX_UB_RES_NUM) + return -EINVAL; + + res = &dev->zone[idx].res; + if (!(res->flags & IORESOURCE_MEM)) + return -EINVAL; + + root = &iomem_resource; + + ret = insert_resource(root, res); + if (ret) { + ub_warn(dev, "failed to claim uent resource %pR\n", res); + return -ENOMEM; + } + + return 0; +} + +static int ub_entity_alloc_mmio_idx(struct ub_entity *dev, int idx) +{ + if (is_ibus_controller(dev) || is_idev(dev)) + return ub_insert_resource(dev, idx); + return ub_assign_resource(dev, idx); +} + +void ub_entity_free_mmio_idx(struct ub_entity *dev, int idx) +{ + ub_release_resource(dev, idx); + dev->zone[idx].init_succ = 0; + dev->zone[idx].ubba_used = 0; + dev->zone[idx].sa_used = 0; +} + +static void fill_decoder_map_info(struct ub_entity *uent, int idx, + struct decoder_map_info *info) +{ + info->pa = uent->zone[idx].res.start; + info->uba = uent->zone[idx].region.start; + info->size = uent->zone[idx].region.size; + info->eid_low = uent->eid; + info->eid_high = 0; + info->tpg_num = is_primary(uent) ? uent->cna : uent->pue->cna; + info->order_id = 0; + info->order_type = 0; + info->token_id = uent->token_id; + info->token_value = uent->token_value; + info->upi = uent->bi ? uent->bi->info.upi : 0; + info->src_eid = uent->bi ? uent->bi->info.eid : 0; +} + +static int ub_entity_decoder_map_mmio_idx(struct ub_entity *uent, int idx) +{ + struct decoder_map_info info = {}; + struct ub_decoder *decoder; + int ret; + + fill_decoder_map_info(uent, idx, &info); + decoder = is_primary(uent) ? uent->ubc->decoder : + uent->pue->ubc->decoder; + + ret = ub_decoder_map(decoder, &info); + if (ret) + ub_err(uent, "resource%d decoder map failed.\n", idx); + + info.token_value = 0; + return ret; +} + +static void ub_entity_decoder_unmap_mmio_idx(struct ub_entity *uent, int idx) +{ + struct ub_decoder *decoder; + + if (!uent->zone[idx].sa_used) + return; + + decoder = is_primary(uent) ? uent->ubc->decoder : + uent->pue->ubc->decoder; + if (ub_decoder_unmap(decoder, uent->zone[idx].res.start, + uent->zone[idx].region.size)) + ub_warn(uent, "resource%d decoder unmap failed.\n", idx); +} + +static bool map_by_ubus(struct ub_entity *ent) +{ + struct ub_bus_controller *ubc = ent->ubc; + u32 feature; + int ret; + + ret = ub_cfg_read_dword(ubc->uent, UB_CFG1_SUPPORT_FEATURE_L, &feature); + if (ret) { + ub_err(ubc->uent, "read ub cfg1 support feature failed, ret=%d\n", + ret); + return false; + } + return !(feature & UB_DECODER_JURIS); +} + +void ub_entity_decoder_unmap_mmio(struct ub_entity *dev) +{ + int i; + + for (i = 0; i < MAX_UB_RES_NUM; i++) + if (dev->zone[i].sa_used && dev->zone[i].init_succ && + dev->zone[i].decoder_mapped) { + ub_entity_decoder_unmap_mmio_idx(dev, i); + dev->zone[i].decoder_mapped = 0; + } +} + +void ub_entity_decoder_map_mmio(struct ub_entity *dev) +{ + int i, ret; + + if (is_ibus_controller(dev)) + return; + + if (!map_by_ubus(dev)) + return; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (!dev->zone[i].sa_used || !dev->zone[i].init_succ) + continue; + if (dev->zone[i].decoder_mapped) { + ub_err(dev, "mmio idx[%d] has been mapped\n", i); + continue; + } + ret = ub_entity_decoder_map_mmio_idx(dev, i); + if (ret) { + ub_err(dev, "failed to establish ommu map, mmio_idx=%d, size=%#lx, error_code=%d\n", + i, dev->zone[i].region.size, ret); + goto fail; + } + dev->zone[i].decoder_mapped = 1; + } + + return; +fail: + for (i -= 1; i >= 0; i--) { + ub_entity_decoder_unmap_mmio_idx(dev, i); + dev->zone[i].decoder_mapped = 0; + } +} + +static int ub_query_token_rsp_handle(struct ub_entity *uent, + struct query_token_msg_pkt *pkt) +{ + struct query_token_msg_pld_rsp *rsp = &pkt->pld.rsp; + + if (pkt->header.msgetah.rsp_status != UB_MSG_RSP_SUCCESS) { + ub_err(uent, "query token rsp, status=%#02x\n", + pkt->header.msgetah.rsp_status); + return -EINVAL; + } + + uent->token_id = rsp->token_id; + uent->token_value = rsp->token_value; + + return 0; +} + +static int ub_query_token_info(struct ub_entity *uent) +{ + struct message_device *mdev = uent->message->mdev; + struct query_token_msg_pkt req_pkt = {}; + struct query_token_msg_pkt rsp_pkt = {}; + struct msg_info info = {}; + int ret; + + ub_msg_pkt_header_init(&req_pkt.header, uent, + QUERY_TOKEN_MSG_REQ_PLD_SIZE, + code_gen(UB_MSG_CODE_SEC, UB_TOKEN_CHECK_CFG, + MSG_REQ), false); + + req_pkt.pld.req.token_enable = 1; + message_info_init(&info, uent, &req_pkt, &rsp_pkt, + (QUERY_TOKEN_MSG_REQ_SIZE << MSG_REQ_SIZE_OFFSET) | + QUERY_TOKEN_MSG_RSP_SIZE); + + ret = message_sync_request(mdev, &info, req_pkt.header.msgetah.code); + if (ret) + return ret; + + return ub_query_token_rsp_handle(uent, &rsp_pkt); +} + +static int ub_entity_writeback_addr(struct ub_entity *uent, int idx) +{ + u32 addr_l_reg[MAX_UB_RES_NUM] = { UB_ERS0_UBBA_L, UB_ERS1_UBBA_L, + UB_ERS2_UBBA_L }; + u32 addr_h_reg[MAX_UB_RES_NUM] = { UB_ERS0_UBBA_H, UB_ERS1_UBBA_H, + UB_ERS2_UBBA_H }; + u32 support_feature, ubba_l, ubba_h; + int ret; + + if (!uent->zone[idx].sa_used) + return 0; + + ret = ub_cfg_read_dword(uent, UB_CFG1_SUPPORT_FEATURE_L, + &support_feature); + if (ret) { + ub_err(uent, + "read reg failed when request sup feature, ret=%d\n", + ret); + return -EINVAL; + } + if (support_feature & UB_UBBAS_SUPPORT) { + ubba_l = (u32)(uent->zone[idx].res.start); + /* Shift right 32 bits to get upper-bit address */ + ubba_h = (u32)((uent->zone[idx].res.start) >> 32); + ub_cfg_write_dword(uent, addr_l_reg[idx], ubba_l); + ub_cfg_write_dword(uent, addr_h_reg[idx], ubba_h); + } + + return 0; +} + +int _ub_entity_setup_mmio(struct ub_entity *dev) +{ + int ret; + int i; + + if (is_ibus_controller(dev)) { + ub_info(dev, "now doesn't support ub bus controller mmio\n"); + return 0; + } + + if (is_device(dev) && !is_p_device(dev)) { + ret = ub_query_token_info(dev); + if (ret) { + ub_err(dev, "Token query failed\n"); + return ret; + } + ub_info(dev, "Token ID=%#x\n", dev->token_id); + } + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + ret = ub_entity_read_mmio_idx(dev, i); + if (ret) + continue; + + ret = ub_entity_alloc_mmio_idx(dev, i); + if (ret) { + ub_err(dev, + "failed to alloc resource for mmio idx %d\n", i); + goto fail; + } + + /* Write back the address reg for the VM scenario capture. */ + ret = ub_entity_writeback_addr(dev, i); + if (ret) { + ub_err(dev, + "failed to write back address for mmio idx %d\n", + i); + ub_entity_free_mmio_idx(dev, i); + goto fail; + } + + ub_info(dev, "MMIO[%d]: ubba=%#lx, size=%#llx, hpa=%#llx, wr attr=0, prefetchable=0, order type=0,\n", + i, dev->zone[i].region.start, ub_resource_len(dev, i), + dev->zone[i].res.start); + dev->zone[i].init_succ = 1; + } + + return 0; + +fail: + for (i -= 1; i >= 0; i--) + if (dev->zone[i].init_succ) + ub_entity_free_mmio_idx(dev, i); + + return ret; +} + +int ub_entity_setup_mmio(struct ub_entity *uent) +{ + int i; + + for (i = 0; i < MAX_UB_RES_NUM; i++) + if (!uent->zone[i].init_succ) + break; + + if (i == MAX_UB_RES_NUM) + return 0; + + return _ub_entity_setup_mmio(uent); +} + +void ub_entity_unset_mmio(struct ub_entity *dev) +{ + int i; + + for (i = 0; i < MAX_UB_RES_NUM; i++) + if (dev->zone[i].init_succ) + ub_entity_free_mmio_idx(dev, i); +} diff --git a/drivers/ub/ubus/resource.h b/drivers/ub/ubus/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..da032502abe83841c48302db5d7bf25fcabc9097 --- /dev/null +++ b/drivers/ub/ubus/resource.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __RESOURCE_H__ +#define __RESOURCE_H__ + +struct ub_entity; +int _ub_entity_setup_mmio(struct ub_entity *uent); +int ub_entity_setup_mmio(struct ub_entity *dev); +void ub_entity_unset_mmio(struct ub_entity *dev); +int ub_mmap_resource_range(struct ub_entity *uent, unsigned long idx, + struct vm_area_struct *vma, int write_combine); +void ub_entity_decoder_map_mmio(struct ub_entity *dev); +void ub_entity_decoder_unmap_mmio(struct ub_entity *uent); +int ub_insert_resource(struct ub_entity *dev, int idx); +void ub_entity_free_mmio_idx(struct ub_entity *dev, int idx); + +#endif /* __RESOURCE_H__ */ diff --git a/drivers/ub/ubus/route.c b/drivers/ub/ubus/route.c new file mode 100644 index 0000000000000000000000000000000000000000..ef3d462a90eec2738950a52bd73fb2f32707ed71 --- /dev/null +++ b/drivers/ub/ubus/route.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus route: " fmt + +#include + +#include "ubus.h" +#include "msg.h" +#include "enum.h" +#include "port.h" +#include "ubus_driver.h" +#include "route.h" + +#define UB_ROUTE_TABLE_ENTRY_START (UB_ROUTE_TABLE_SLICE_START + (0x10 << 2)) +#define EBW(port_nums) ((((port_nums) - 1) >> 5) + 1) /* Entry Bit Width */ +#define UB_ROUTE_TABLE_ENTRY_BITS SZ_128 +#define UB_ROUTE_KFIFO_DEPTH SZ_16 + +/* node to update routing table */ +struct cna_node { + u32 cna; + u16 distance; /* distance of path */ + u16 count; /* number of ports that have route to this cna */ + bool need_update; /* need to write to routing table */ + + struct kref ref; + struct list_head node; +}; + +static struct cna_node *ub_create_cna_node(u32 cna, short distance) +{ + struct cna_node *new_cna; + + new_cna = kzalloc(sizeof(*new_cna), GFP_KERNEL); + if (!new_cna) + return NULL; + + new_cna->cna = cna; + new_cna->need_update = true; + new_cna->distance = distance; + new_cna->count = 1; + kref_init(&new_cna->ref); + INIT_LIST_HEAD(&new_cna->node); + + return new_cna; +} + +static void ub_cna_node_release(struct kref *kref) +{ + struct cna_node *cna = container_of(kref, struct cna_node, ref); + + kfree(cna); +} + +static struct cna_node *ub_cna_node_get(struct cna_node *cna) +{ + kref_get(&cna->ref); + return cna; +} + +static void ub_cna_node_put(struct cna_node *cna) +{ + kref_put(&cna->ref, ub_cna_node_release); +} + +static int ub_add_cna_node(u32 cna, short distance, struct list_head *cna_list) +{ + struct list_head *prev_head = cna_list; + struct cna_node *new_cna, *prev_cna; + + /** + * keep the cna list in ascending order to get the smallest cna quickly + * traverse from tail to end, insert new node when prev is smaller + */ + list_for_each_entry_reverse(prev_cna, cna_list, node) { + if (prev_cna->cna > cna) + continue; + + if (prev_cna->cna == cna) { + prev_cna->count++; + return 0; + } + + prev_head = &prev_cna->node; + break; + } + + new_cna = ub_create_cna_node(cna, distance); + if (!new_cna) + return -ENOMEM; + + list_add(&new_cna->node, prev_head); + return 0; +} + +static struct cna_node *ub_find_cna_node(u32 cna, struct list_head *cna_list) +{ + struct cna_node *cna_node; + + list_for_each_entry(cna_node, cna_list, node) { + if (cna_node->cna < cna) + continue; + + if (cna_node->cna > cna) + break; + + return ub_cna_node_get(cna_node); + } + + return NULL; +} + +static void ub_clear_cna_list(struct list_head *cna_list) +{ + struct cna_node *cna, *tmp; + + list_for_each_entry_safe(cna, tmp, cna_list, node) { + list_del(&cna->node); + ub_cna_node_put(cna); + } +} + +/* the cna_list had better be empty since it'll be clear when err comes */ +static int ub_entity_copy_cna_list(struct ub_entity *uent, struct list_head *cna_list, + bool update_only) +{ + struct cna_node *cna, *new_cna; + + list_for_each_entry(cna, &uent->cna_list, node) { + if (update_only && !cna->need_update) + continue; + + new_cna = ub_create_cna_node(cna->cna, cna->distance); + if (!new_cna) { + ub_clear_cna_list(cna_list); + return -ENOMEM; + } + list_add_tail(&new_cna->node, cna_list); + } + + return 0; +} + +void ub_route_clear(struct ub_entity *uent) +{ + struct ub_port *port; + + if (!uent) + return; + + for_each_uent_port(port, uent) + bitmap_clear(port->cna_maps, 0, UB_ROUTE_TABLE_ENTRY_BITS); + + ub_clear_cna_list(&uent->cna_list); +} + +int ub_route_add_entry(struct ub_port *port, u32 cna, short distance) +{ + if (!port) + return -EINVAL; + + if (test_bit(cna, port->cna_maps)) + return -EEXIST; + + set_bit(cna, port->cna_maps); + return ub_add_cna_node(cna, distance, &port->uent->cna_list); +} + +static void ub_route_del_entry(struct ub_port *port, u32 cna) +{ + struct cna_node *node; + + clear_bit(cna, port->cna_maps); + node = ub_find_cna_node(cna, &port->uent->cna_list); + if (!node) { + ub_err(port->uent, "can not find cna %#x from uent cna list\n", + cna); + return; + } + + node->need_update = true; + node->count--; + ub_cna_node_put(node); +} + +static bool ub_route_del_entries(struct ub_port *port, + struct list_head *cna_list) +{ + struct ub_entity *uent = port->uent; + bool route_updated = false; + struct cna_node *cna; + + list_for_each_entry(cna, cna_list, node) { + if (!test_bit(cna->cna, port->cna_maps)) + continue; + + ub_route_del_entry(port, cna->cna); + route_updated = true; + } + + if (route_updated) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, true); + + return route_updated; +} + +static bool ub_entity_has_cna(u32 cna, struct ub_entity *uent) +{ + struct ub_port *port; + + if (uent->cna == cna) + return true; + + for_each_uent_port(port, uent) + if (port->cna == cna) + return true; + + return false; +} + +/* try update routing table from cna_list, return true if updated */ +static bool ub_route_mod_entries(struct ub_port *port, + struct list_head *cna_list, short off) +{ + struct ub_entity *uent = port->uent; + struct cna_node *cna, *uent_cna; + bool route_updated = false; + short new_distance; + int ret; + + list_for_each_entry(cna, cna_list, node) { + if (test_bit(cna->cna, port->cna_maps)) + continue; + + if (ub_entity_has_cna(cna->cna, uent)) + continue; + + new_distance = cna->distance + off; + + uent_cna = ub_find_cna_node(cna->cna, &uent->cna_list); + if (!uent_cna) { + ret = ub_route_add_entry(port, cna->cna, new_distance); + if (!ret) + goto set_flag; + + ub_err(uent, "add route entry for %#x failed with %d\n", + cna->cna, ret); + return false; + } + + if (uent_cna->distance < new_distance) { + ub_cna_node_put(uent_cna); + continue; + } + + /* in bfs we will never encounter a shorter path */ + set_bit(cna->cna, port->cna_maps); + uent_cna->need_update = true; + uent_cna->count++; + ub_cna_node_put(uent_cna); +set_flag: + route_updated = true; + } + + if (route_updated) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, true); + + return route_updated; +} + +bool ub_entity_support_forward(struct ub_entity *uent) +{ + if (!uent) + return false; + + if (is_ibus_controller(uent)) + return true; + + if (is_switch(uent)) + return true; + + return false; +} + +static bool ub_check_path(struct ub_port *from, struct ub_port *to) +{ + if (to == from) + return false; + + if (!to->r_uent) + return false; + + if (to->r_uent->port_nums <= 1) + return false; + + return true; +} + +/** + * consider a given topo like + * +-------------+ +---------+ + * | controller0 |p0:---:p1| device0 | + * +-------------+ +---------+ + * when ub_route_mod_neighbor is called for p0, p1, device0's routing table + * will be updated with p0'cna and controller0's cna. If controller0 supports + * forward, device0 will also know all route in controller0's routing table + */ +int ub_route_mod_neighbor(struct ub_port *port, struct ub_port *r_port) +{ + struct list_head cna_list; + int ret; + + if (!port || !r_port) + return -EINVAL; + + INIT_LIST_HEAD(&cna_list); + + /* if uent can not forward, only need to update uent & port's cna */ + if (ub_entity_support_forward(port->uent)) { + ret = ub_entity_copy_cna_list(port->uent, &cna_list, false); + if (ret) + return ret; + } + + ret = ub_add_cna_node(port->uent->cna, 0, &cna_list); + if (ret) + goto clear_cna; + + ret = ub_add_cna_node(port->cna, 0, &cna_list); + if (ret) + goto clear_cna; + + ub_route_mod_entries(r_port, &cna_list, 1); + +clear_cna: + ub_clear_cna_list(&cna_list); + return ret; +} + +/** + * consider a given topo like + * +-------------+ +---------+ + * | controller0 |p0:---:p1| device0 | + * +-------------+ +---------+ + * when device0 is being removed, ub_route_clear_port is called for p0 + * 1. all route that pass though this p0 will be del from controller0's cna_list + * 2. p0's cna_maps will be clear + */ +void ub_route_clear_port(struct ub_port *port) +{ + struct list_head *cna_list; + struct cna_node *cna_node; + bool route_update = false; + struct ub_entity *uent; + u32 cna; /* use u32 to avoid wraparound */ + + if (!port) + return; + + uent = port->uent; + cna_list = &uent->cna_list; + cna_node = list_first_entry(cna_list, struct cna_node, node); + cna = find_next_bit(port->cna_maps, UB_MAX_CNA_NUM, 0); + + while (cna < UB_MAX_CNA_NUM) { + if (list_entry_is_head(cna_node, cna_list, node)) + goto clear_bit; + + while (cna_node->cna < cna) { + cna_node = list_next_entry(cna_node, node); + if (list_entry_is_head(cna_node, cna_list, node)) { + ub_warn(uent, "can't find cna %u in list\n", cna); + goto clear_bit; + } + } + + if (cna_node->cna == cna) { + route_update = true; + cna_node->count--; + cna_node->need_update = true; + } +clear_bit: + clear_bit(cna, port->cna_maps); + cna = find_next_bit(port->cna_maps, UB_MAX_CNA_NUM, cna + 1); + } + + if (route_update) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, true); +} + +struct bfs_node { + struct ub_port *from; + short off; +}; + +/** + * consider a given topo like + * +-------------+ +---------+ +--------+ + * | controller0 |p0:---:p0| switch0 |p1:---slot0---:p0| device0| + * +-------------+ +---------+ +--------+ + * after device0 is plugged in, switch0 updates route with p0 and device0's cna, + * ub_route_mod_bfs tries to use the updated route in switch0's routing + * table to update controller0 through p0. If success, then pass through + * controller0's other ports to update neighbor + */ +void ub_route_mod_bfs(struct ub_entity *uent) +{ + DECLARE_KFIFO(kfifo, struct bfs_node, UB_ROUTE_KFIFO_DEPTH); + struct bfs_node curr, next; + struct list_head cna_list; + struct ub_port *from, *to; + + if (!uent) + return; + + INIT_KFIFO(kfifo); + INIT_LIST_HEAD(&cna_list); + + if (ub_entity_copy_cna_list(uent, &cna_list, true)) + goto clear_cna; + + for_each_uent_port(to, uent) { + if (to->r_uent && to->r_uent->port_nums > 1) { + next.from = to->r_uent->ports + to->r_index; + next.off = 1; + kfifo_put(&kfifo, next); + } + } + + while (kfifo_get(&kfifo, &curr)) { + from = curr.from; + if (!ub_route_mod_entries(from, &cna_list, curr.off)) + continue; + + if (!ub_entity_support_forward(from->uent)) + continue; + + for_each_uent_port(to, from->uent) { + if (!ub_check_path(from, to)) + continue; + + next.from = to->r_uent->ports + to->r_index; + next.off = curr.off + 1; + if (!kfifo_put(&kfifo, next)) + ub_err(next.from->uent, + "%s kfifo put failed!\n", __func__); + } + } + +clear_cna: + ub_clear_cna_list(&cna_list); +} + +/* same as ub_route_mod_bfs, but used to del route instead of update route */ +void ub_route_del_bfs(struct ub_entity *uent) +{ + DECLARE_KFIFO(kfifo, struct ub_port *, UB_ROUTE_KFIFO_DEPTH); + struct ub_port *from, *to; + struct list_head cna_list; + + if (!uent) + return; + + INIT_KFIFO(kfifo); + INIT_LIST_HEAD(&cna_list); + + if (ub_entity_copy_cna_list(uent, &cna_list, true)) + goto clear_cna; + + for_each_uent_port(to, uent) + if (to->r_uent && to->r_uent->port_nums > 1) + kfifo_put(&kfifo, to->r_uent->ports + to->r_index); + + while (kfifo_get(&kfifo, &from)) { + if (!ub_route_del_entries(from, &cna_list)) + continue; + + if (!ub_entity_support_forward(from->uent)) + continue; + + for_each_uent_port(to, from->uent) { + if (!ub_check_path(from, to)) + continue; + + if (!kfifo_put(&kfifo, to->r_uent->ports + to->r_index)) + ub_err(to->r_uent, + "%s kfifo put failed!\n", __func__); + } + } + +clear_cna: + ub_clear_cna_list(&cna_list); +} + +static void ub_set_route_table_entry(struct ub_entity *uent, u32 dst_cna, + u32 *route_table_entry) +{ + int i; + u32 offset; + + /* Routing Table Block is not required for single-port devices. */ + if (uent->port_nums == 1) + return; + + /* In a cluster scenario, do not configure the UBC routing table. */ + if (is_ibus_controller(uent) && uent->ubc->cluster) + return; + + pr_info("cna %#x uent set dstcna %#x route\n", uent->cna, dst_cna); + + for (i = 0; i < EBW(uent->port_nums); i++) { + offset = ((dst_cna + 1) * EBW(uent->port_nums) + i) << SZ_2; + ub_cfg_write_dword(uent, UB_ROUTE_TABLE_ENTRY_START + offset, + route_table_entry[i]); + } +} + +/** + * after updating routing table in software, this function must be called + * to sync the software routing table to config space + */ +void ub_route_sync_dev(struct ub_entity *uent) +{ + DECLARE_BITMAP(route_table_entry, UB_ROUTE_TABLE_ENTRY_BITS); + struct cna_node *cna_node, *tmp; + struct ub_port *port; + u32 cna; + + if (!uent || !ub_entity_test_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED)) + return; + + list_for_each_entry_safe(cna_node, tmp, &uent->cna_list, node) { + if (!cna_node->need_update) + continue; + + cna = cna_node->cna; + bitmap_zero(route_table_entry, UB_ROUTE_TABLE_ENTRY_BITS); + + if (cna_node->count == 0) { + list_del(&cna_node->node); + kfree(cna_node); + goto set_entry; + } + + for_each_uent_port(port, uent) + if (test_bit(cna, port->cna_maps)) + set_bit(port->index, route_table_entry); + + cna_node->need_update = false; +set_entry: + ub_set_route_table_entry(uent, cna, (u32 *)route_table_entry); + } + + ub_entity_assign_priv_flag(uent, UB_ENTITY_ROUTE_UPDATED, false); +} + +/** + * this function sync all updated routing table entry to config space + * for devices that aren't in ubc dev list, this function will not update them + */ +void ub_route_sync_all(void) +{ + struct ub_bus_controller *ubc; + struct ub_entity *uent; + + down_read(&ub_bus_sem); + list_for_each_entry(ubc, &ubc_list, node) + list_for_each_entry(uent, &ubc->devs, node) + ub_route_sync_dev(uent); + up_read(&ub_bus_sem); +} + +int ub_route_entities(struct list_head *dev_list) +{ + if (!dev_list) + return -EINVAL; + + return ub_enum_bfs_route_cal(dev_list); +} + +void ub_route_table_clear_for_port(struct ub_port *port, struct ub_port *r_port) +{ + ub_route_clear_port(r_port); + + ub_route_clear_port(port); + + if (ub_entity_support_forward(r_port->uent)) + ub_route_del_bfs(r_port->uent); + + if (ub_entity_support_forward(port->uent)) + ub_route_del_bfs(port->uent); + + ub_route_sync_all(); +} + +int ub_route_table_set_for_port(struct ub_port *port, + struct ub_port *r_port) +{ + int ret; + + ret = ub_route_mod_neighbor(port, r_port); + if (ret) + return ret; + + ret = ub_route_mod_neighbor(r_port, port); + if (ret) + return ret; + + if (ub_entity_support_forward(port->uent)) + ub_route_mod_bfs(port->uent); + + if (ub_entity_support_forward(r_port->uent)) + ub_route_mod_bfs(r_port->uent); + + ub_route_sync_all(); + + return 0; +} diff --git a/drivers/ub/ubus/route.h b/drivers/ub/ubus/route.h new file mode 100644 index 0000000000000000000000000000000000000000..d7d96d86d4de3b8741c3ccc3dd67f8a181218029 --- /dev/null +++ b/drivers/ub/ubus/route.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __ROUTE_H__ +#define __ROUTE_H__ + +struct ub_entity; +struct ub_port; +void ub_route_clear(struct ub_entity *uent); +int ub_route_add_entry(struct ub_port *port, u32 cna, short distance); +bool ub_entity_support_forward(struct ub_entity *uent); +int ub_route_mod_neighbor(struct ub_port *port, struct ub_port *r_port); +void ub_route_clear_port(struct ub_port *port); +void ub_route_mod_bfs(struct ub_entity *uent); +void ub_route_del_bfs(struct ub_entity *uent); +void ub_route_sync_dev(struct ub_entity *uent); +void ub_route_sync_all(void); +int ub_route_entities(struct list_head *dev_list); +void ub_route_table_clear_for_port(struct ub_port *port, + struct ub_port *r_port); +int ub_route_table_set_for_port(struct ub_port *port, struct ub_port *r_port); + +#endif /* __ROUTE_H__ */ diff --git a/drivers/ub/ubus/services.h b/drivers/ub/ubus/services.h new file mode 100644 index 0000000000000000000000000000000000000000..71fe6aa5735795ee5c53f632899463dbda1d2960 --- /dev/null +++ b/drivers/ub/ubus/services.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __SERVICES_H__ +#define __SERVICES_H__ + +int ub_services_init(void); +void ub_services_exit(void); + +#endif /* __SERVICES_H__ */ diff --git a/drivers/ub/ubus/services/gucd.c b/drivers/ub/ubus/services/gucd.c new file mode 100644 index 0000000000000000000000000000000000000000..4ed4f53b9b7d2da6a5afba99ba766b43be1ebc66 --- /dev/null +++ b/drivers/ub/ubus/services/gucd.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus gucd: " fmt + +#include "../ubus.h" +#include "../decoder.h" +#include "../ubus_driver.h" +#include "../memory.h" +#include "service.h" + +static const struct ub_device_id component_device_ids[] = { + { UB_ENTITY_CLASS(UB_CLASS_BUS_CONTROLLER, (u16)~0)}, + { UB_ENTITY_CLASS(UB_CLASS_SWITCH_UB, (u16)~0)}, + {} +}; +MODULE_DEVICE_TABLE(ub, component_device_ids); + +static int get_component_service_capability(struct ub_entity *uent) +{ + int services = 0; + u32 val; + int ret; + + ret = ub_cfg_read_dword(uent, UB_CFG0_CAP_BITMAP, &val); + if (ret) + return services; + + if (val & (1 << UB_SHP_CAP)) + services |= UB_SERVICE_HP; + + return services; +} + +static void release_service_device(struct device *dev) +{ + kfree(to_ub_service_device(dev)); +} + +static int ub_component_service_init(struct ub_entity *uent, int service) +{ + struct ub_service_device *sdev; + struct device *dev; + int ret; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + sdev->uent = uent; + sdev->service = service; + + dev = &sdev->device; + dev->bus = &ub_service_bus_type; + dev->release = release_service_device; + dev_set_name(dev, "%s:service%03x", ub_name(uent), (u32)service); + dev->parent = &uent->dev; + + ret = device_register(dev); + if (ret) + put_device(dev); + + return ret; +} + +static int ub_component_service_register(struct ub_entity *uent) +{ + int nr_service = 0; + int capabilities; + int i; + + if (is_ibus_controller(uent) && uent->ubc->cluster) + return 0; + + /* Get and check component services */ + capabilities = get_component_service_capability(uent); + if (!capabilities) + return 0; + + for (i = 0; i < UB_MAXSERIVCES; i++) { + int service = 1 << i; + + if (!(capabilities & service)) + continue; + if (!ub_component_service_init(uent, service)) + nr_service++; + } + + if (!nr_service) + return -ENODEV; + + return 0; +} + +static void ub_setup_bus_controller(struct ub_entity *uent) +{ + u32 vec_num_max; + int usi_count; + + if (ub_cc_supported(uent) && !uent->ubc->cluster) + ub_cc_enable(uent); + + ub_set_user_info(uent); + vec_num_max = ub_int_type1_vec_count(uent); + usi_count = ub_alloc_irq_vectors(uent, vec_num_max, vec_num_max); + if (usi_count < 0) { + ub_err(uent, "alloc irq for ub bus controller failed, usi_count=%d\n", + usi_count); + return; + } + + if ((u32)usi_count < vec_num_max) { + ub_err(uent, "alloc irq vectors failed, usi count=%d, vec_num_max=%u\n", + usi_count, vec_num_max); + } else { + ub_init_decoder_usi(uent); + ub_mem_init_usi(uent); + } +} + +static void ub_unset_bus_controller(struct ub_entity *uent) +{ + ub_mem_uninit_usi(uent); + ub_uninit_decoder_usi(uent); + ub_disable_intr(uent); + ub_unset_user_info(uent); + + if (ub_cc_supported(uent) && !uent->ubc->cluster) + ub_cc_disable(uent); +} + +static ub_ers_result_t ub_gucd_err_detected(struct ub_entity *uent, ub_channel_state_t state) +{ + if (state == ub_channel_io_normal) + return UB_ERS_RESULT_NEED_RESET; + + if (state == ub_channel_io_frozen) + return UB_ERS_RESULT_NEED_RESET; + + return UB_ERS_RESULT_CAN_RECOVER; +} + +static const struct ub_error_handlers gucd_err_handler = { + .ub_error_detected = ub_gucd_err_detected, +}; + +static int ub_component_probe(struct ub_entity *uent, + const struct ub_device_id *id) +{ + if (is_ibus_controller(uent)) + ub_setup_bus_controller(uent); + + return ub_component_service_register(uent); +} + +static int remove_iter(struct device *dev, void *data) +{ + if (dev->bus == &ub_service_bus_type) + device_unregister(dev); + + return 0; +} + +static void ub_component_remove(struct ub_entity *uent) +{ + device_for_each_child(&uent->dev, NULL, remove_iter); + if (is_ibus_controller(uent)) + ub_unset_bus_controller(uent); +} + +static struct ub_driver ub_component_device_driver = { + .name = "ub_generic_component", + .id_table = component_device_ids, + .probe = ub_component_probe, + .remove = ub_component_remove, + .err_handler = &gucd_err_handler, + .driver_managed_dma = true, +}; + +static void ub_init_services(void) +{ + ub_ras_init(); + ubhp_service_init(); +} + +static void ub_uninit_services(void) +{ + ubhp_service_uninit(); + ub_ras_uninit(); +} + +int ub_services_init(void) +{ + ub_init_services(); + + return ub_register_driver(&ub_component_device_driver); +} + +void ub_services_exit(void) +{ + ub_unregister_driver(&ub_component_device_driver); + ub_uninit_services(); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug.h b/drivers/ub/ubus/services/hotplug/hotplug.h new file mode 100644 index 0000000000000000000000000000000000000000..81f731eb29beded24995f4a91a39096a3dac3d93 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __HOTPLUG_H__ +#define __HOTPLUG_H__ + +/** + * struct ub_slot - UB hotplug slot + * @uent: pointer to the ub dev who has this slot + * @r_uent: pointer to the ub dev who is plugged in this slot + * @ports: port array slice of uent's port array + * @kobj: kobject of slot to provide sysfs + * @node: node of slot in uent slot_list; + * + * @slot_id: id of this slot + * @port_start: start port_idx of ports belong to this slot + * @port_num: number of ports belong to this slot + * + * @button_work: work to queue for button pressed event + * @present_work: work to queue for card present event + * @power_work: work to queue for power state update + * @state: current state machine position + * @state_lock: mutex lock for slot state + */ +struct ub_slot { + /* slot info */ + struct ub_entity *uent; + struct ub_entity *r_uent; + struct ub_port *ports; + struct kobject kobj; + struct list_head node; + + /* cap info */ + u8 slot_id; + u16 port_start; + u16 port_num; + u32 slot_cap; + + /* hotplug info */ + struct work_struct button_work; + struct delayed_work present_work; + struct delayed_work power_work; + u8 state; + struct mutex state_lock; +}; + +#define for_each_slot_port(p, s) \ + for ((p) = (s)->ports; ((p) - (s)->ports) < (s)->port_num; (p)++) + +#define BUTTON(slot) ((slot)->slot_cap & UB_SLOT_PPS) +#define WORK_LED(slot) ((slot)->slot_cap & UB_SLOT_WLPS) +#define PWR_LED(slot) ((slot)->slot_cap & UB_SLOT_PLPS) +#define PRESENT(slot) ((slot)->slot_cap & UB_SLOT_PDSS) +#define PWR(slot) ((slot)->slot_cap & UB_SLOT_PWCS) + +struct ubhp_msg_payload { + u16 slot_id; + u16 rsv0; + u32 rsv1; +}; + +#define UB_HP_MSG_SIZE 40 + +enum power_state { + POWER_OFF, + POWER_ON +}; + +enum indicator_state { + INDICATOR_OFF, /* set indicator off */ + INDICATOR_ON, /* set indicator on */ + INDICATOR_BLINKING, /* set indicator blinking */ + INDICATOR_NOOP /* left indicator unchanged */ +}; + +enum hotplug_event { + HPE_BUTTON_PRESSED = 2, + HPE_PRESENCE_DETECT, + HPE_OTHER +}; + +/** + * a slot must be get before any of its work is queued into workqueue + * and must be put after the work is end + * so that when a slot is released, its works are idle and don't need to cancel + */ +static inline struct ub_slot *ubhp_get_slot(struct ub_slot *slot) +{ + if (slot) + kobject_get(&slot->kobj); + + return slot; +} + +static inline void ubhp_put_slot(struct ub_slot *slot) +{ + if (slot) + kobject_put(&slot->kobj); +} + +/* ctrl */ +int ub_slot_read_dword(struct ub_slot *slot, u32 pos, u32 *val); +void ubhp_set_indicators(struct ub_slot *slot, u8 power, u8 work); +void ubhp_set_slot_power(struct ub_slot *slot, enum power_state power); +bool ubhp_confirm_event(struct ub_slot *slot, enum hotplug_event event); +bool ubhp_wait_linkup(struct ub_slot *slot); +bool ubhp_card_present(struct ub_slot *slot); +void ubhp_start_slots(struct ub_entity *uent); +void ubhp_stop_slots(struct ub_entity *uent); + +/* msg */ +void ubhp_event_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +/* core */ +void ubhp_handle_power(struct ub_slot *slot, bool power_on); + +/* route */ +int ubhp_update_route_link_up(struct ub_slot *slot); +void ubhp_update_route_link_down(struct ub_slot *slot); +void ubhp_mark_detached_entities(struct ub_entity *root, struct list_head *dev_list); +void ubhp_stop_entities(struct list_head *dev_list); +void ubhp_remove_entities(struct list_head *dev_list); + +#endif /* __HOTPLUG_H__ */ diff --git a/drivers/ub/ubus/services/hotplug/hotplug_core.c b/drivers/ub/ubus/services/hotplug/hotplug_core.c new file mode 100644 index 0000000000000000000000000000000000000000..eed9927802d2d60168e2983f3c1eb74e458eb82b --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_core.c @@ -0,0 +1,689 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../enum.h" +#include "../../route.h" +#include "../../port.h" +#include "../service.h" +#include "hotplug.h" + +#define to_ub_slot(s) container_of(s, struct ub_slot, kobj) + +static void ubhp_destroy_slot(struct ub_slot *slot) +{ + mutex_destroy(&slot->state_lock); + kfree(slot); +} + +static void ubhp_slot_release(struct kobject *kobj) +{ + struct ub_slot *slot = to_ub_slot(kobj); + + ubhp_destroy_slot(slot); +} + +enum slot_state { + SLOT_ON, /* slot on, device running */ + SLOT_POWERON, /* from slot off to slot on */ + SLOT_POWEROFF, /* from slot on to slot off */ + SLOT_OFF, /* slot off, device not present */ +}; + +struct ub_slot_attribute { + struct attribute attr; + ssize_t (*show)(struct ub_slot *slot, char *buf); + ssize_t (*store)(struct ub_slot *slot, const char *buf, size_t count); +}; + +#define to_ub_slot_attr(s) container_of(s, struct ub_slot_attribute, attr) + +static void ubhp_enable_slot(struct ub_slot *slot) +{ + bool queued = false; + + ubhp_get_slot(slot); + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_OFF) + queued = queue_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->button_work); + else + ub_info(slot->uent, "ignore slot poweron\n"); + + mutex_unlock(&slot->state_lock); + if (!queued) + ubhp_put_slot(slot); +} + +static void ubhp_disable_slot(struct ub_slot *slot) +{ + bool queued = false; + + ubhp_get_slot(slot); + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_ON) + queued = queue_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->button_work); + else + ub_info(slot->uent, "ignore slot poweroff\n"); + + mutex_unlock(&slot->state_lock); + if (!queued) + ubhp_put_slot(slot); +} + +static ssize_t power_show(struct ub_slot *slot, char *buf) +{ + ssize_t ret; + + mutex_lock(&slot->state_lock); + switch (slot->state) { + case SLOT_ON: + ret = sysfs_emit(buf, "slot is %s\n", "on"); + break; + case SLOT_POWERON: + ret = sysfs_emit(buf, "slot is %s\n", "poweron"); + break; + case SLOT_POWEROFF: + ret = sysfs_emit(buf, "slot is %s\n", "poweroff"); + break; + case SLOT_OFF: + ret = sysfs_emit(buf, "slot is %s\n", "off"); + break; + default: + ret = sysfs_emit(buf, "unknown state %u\n", slot->state); + break; + } + mutex_unlock(&slot->state_lock); + + return ret; +} + +static ssize_t power_store(struct ub_slot *slot, const char *buf, size_t count) +{ + unsigned long power; + int ret; + + ret = kstrtoul(buf, 0, &power); + if (ret) { + ub_err(slot->uent, "Invalid val for power\n"); + return ret; + } + + switch (power) { + case 0: + ubhp_disable_slot(slot); + break; + case 1: + ubhp_enable_slot(slot); + break; + default: + ub_err(slot->uent, "Invalid val %lu for power\n", power); + return -EINVAL; + } + + return count; +} +static struct ub_slot_attribute ub_slot_attr_power = __ATTR_RW(power); + +static struct attribute *ub_slot_default_attrs[] = { + &ub_slot_attr_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(ub_slot_default); + +static ssize_t ub_slot_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct ub_slot_attribute *attribute = to_ub_slot_attr(attr); + struct ub_slot *slot = to_ub_slot(kobj); + + if (!attribute->show) + return -EIO; + + return attribute->show(slot, buf); +} + +static ssize_t ub_slot_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct ub_slot_attribute *attribute = to_ub_slot_attr(attr); + struct ub_slot *slot = to_ub_slot(kobj); + + if (!attribute->store) + return -EIO; + + return attribute->store(slot, buf, count); +} + +static const struct sysfs_ops ub_slot_sysfs_ops = { + .show = ub_slot_attr_show, + .store = ub_slot_attr_store, +}; + +static const struct kobj_type ub_slot_ktype = { + .release = ubhp_slot_release, + .sysfs_ops = &ub_slot_sysfs_ops, + .default_groups = ub_slot_default_groups, +}; + +static void ubhp_button_handler(struct work_struct *work); +static void ubhp_present_handler(struct work_struct *work); +static void ubhp_power_handler(struct work_struct *work); +static struct ub_slot *ubhp_create_slot(void) +{ + struct ub_slot *slot; + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) + return NULL; + + slot->ports = NULL; + INIT_LIST_HEAD(&slot->node); + INIT_WORK(&slot->button_work, ubhp_button_handler); + INIT_DELAYED_WORK(&slot->present_work, ubhp_present_handler); + INIT_DELAYED_WORK(&slot->power_work, ubhp_power_handler); + mutex_init(&slot->state_lock); + + return slot; +} + +static int ubhp_slot_port_check(struct ub_entity *uent, int idx, u32 val) +{ + struct ub_port *ports, *tmp; + u16 port_start, port_end; + u16 port_num; + + port_start = (u16)(val & UB_SLOT_START_PORT); + port_end = (u16)(val >> SZ_16); + + if (port_start > port_end || port_end >= uent->port_nums) { + ub_err(uent, "slot%d port range err, start=%u, end=%u, total=%u\n", + idx, port_start, port_end, uent->port_nums); + return -EINVAL; + } + + port_num = port_end - port_start + 1; + ports = uent->ports + port_start; + for (tmp = ports; (tmp - ports) < port_num; tmp++) { + if (tmp->type == VIRTUAL) { + ub_err(uent, "slot%d port%u type is virtual\n", + idx, tmp->index); + return -EINVAL; + } + + if (tmp->slot) { + ub_err(uent, "slot%d port%u already in slot%u\n", + idx, tmp->index, tmp->slot->slot_id); + return -EINVAL; + } + } + + return 0; +} + +static int ubhp_setup_slot(struct ub_slot *slot, struct ub_entity *uent, int idx) +{ + u16 port_start, port_end; + u32 val, cap; + int ret; + + slot->uent = uent; + slot->slot_id = idx; + + ret = ub_slot_read_dword(slot, UB_SLOT_PORT, &val); + if (ret) + return ret; + + ret = ubhp_slot_port_check(uent, idx, val); + if (ret) + return ret; + + ret = ub_slot_read_dword(slot, UB_SLOT_CAP, &cap); + if (ret) + return ret; + + port_start = (u16)(val & UB_SLOT_START_PORT); + port_end = (u16)(val >> SZ_16); + + slot->port_start = port_start; + slot->port_num = port_end - port_start + 1; + slot->ports = uent->ports + port_start; + slot->slot_cap = cap; + + ub_info(uent, "slot%u port[%u:%u] cap[%#x] setup\n", + slot->slot_id, slot->port_start, port_end, slot->slot_cap); + + return 0; +} + +static void ubhp_del_slot(struct ub_slot *slot) +{ + struct ub_port *port; + + cancel_work_sync(&slot->button_work); + cancel_delayed_work_sync(&slot->power_work); + cancel_delayed_work_sync(&slot->present_work); + + list_del(&slot->node); + + for_each_slot_port(port, slot) + port->slot = NULL; + + kobject_del(&slot->kobj); +} + +static int ubhp_add_slot(struct ub_slot *slot) +{ + struct ub_entity *uent = slot->uent; + struct ub_port *port; + int ret; + + ret = kobject_init_and_add(&slot->kobj, &ub_slot_ktype, &uent->dev.kobj, + "slot%d", slot->slot_id); + if (ret) + return ret; + + if (slot->ports->r_uent) { + slot->state = SLOT_ON; + slot->r_uent = slot->ports->r_uent; + } else { + slot->state = SLOT_OFF; + } + + for_each_slot_port(port, slot) + port->slot = slot; + + list_add(&slot->node, &uent->slot_list); + + return ret; +} + +static int ubhp_probe(struct ub_service_device *sdev) +{ + struct ub_entity *uent = sdev->uent; + struct ub_slot *slot, *tmp; + u16 slot_num; + int i, ret; + + if (sdev->service != UB_SERVICE_HP) + return -ENODEV; + + ret = ub_cfg_read_word(uent, UB_SLOT_START + UB_SLOT_NUM, &slot_num); + if (ret) + return ret; + + if (!slot_num) { + ub_err(uent, "hotplug probe without slot, ignoring\n"); + return -ENODEV; + } + + for (i = 0; i < slot_num; i++) { + slot = ubhp_create_slot(); + if (!slot) { + ret = -ENOMEM; + goto free_slots; + } + + ret = ubhp_setup_slot(slot, uent, i); + if (ret) { + ubhp_destroy_slot(slot); + goto free_slots; + } + + ret = ubhp_add_slot(slot); + if (ret) { + ubhp_put_slot(slot); + goto free_slots; + } + } + + ubhp_start_slots(uent); + + return 0; +free_slots: + list_for_each_entry_safe_reverse(slot, tmp, &uent->slot_list, node) { + ubhp_del_slot(slot); + ubhp_put_slot(slot); + } + + return ret; +} + +static void ubhp_remove(struct ub_service_device *sdev) +{ + struct ub_entity *uent = sdev->uent; + struct ub_slot *slot, *tmp; + + ubhp_stop_slots(uent); + + list_for_each_entry_safe(slot, tmp, &uent->slot_list, node) { + ubhp_del_slot(slot); + ubhp_put_slot(slot); + } +} + +static struct ub_service_driver ubhp_service_driver = { + .name = "ub-hotplug", + + .probe = ubhp_probe, + .remove = ubhp_remove, + + .service = UB_SERVICE_HP, +}; + +void ubhp_service_init(void) +{ + ub_service_driver_register(&ubhp_service_driver); +} + +void ubhp_service_uninit(void) +{ + ub_service_driver_unregister(&ubhp_service_driver); +} + +static void ubhp_disconnect_slot(struct ub_slot *slot) +{ + struct ub_port *port; + + for_each_slot_port(port, slot) + ub_port_disconnect(port); + + slot->r_uent = NULL; +} + +static void ubhp_clear_port(struct ub_slot *slot) +{ + struct ub_port *port; + + for_each_slot_port(port, slot) { + port->r_index = 0; + guid_copy(&port->r_guid, &guid_null); + } +} + +/** + * ubhp_enum_at_slot() - enum at slot to find new devices + * @slot: the slot that has new device plugged in + * @dev_list: a list to store the new found devices + * + * this func use bfs to enum devices and put them into dev_list, + * which means the previous device in dev_list is enumerated previous + */ +static int ubhp_enum_at_slot(struct ub_slot *slot, struct list_head *dev_list) +{ + void *buf; + int ret; + +#define UB_TOPO_BUF_SZ SZ_4K + buf = kzalloc(UB_TOPO_BUF_SZ, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = ub_enum_topo_scan_ports(slot->uent, slot->port_start, slot->port_num, + dev_list, buf); + if (ret) + ubhp_clear_port(slot); + + kfree(buf); + return ret; +} + +/** + * a simple example for link up + * for a given topo like + * +-------------+ +---------+ +---------+ +--------+ + * | controller0 |p0:---:p0| switch0 |p1:---slot0---:p0| switch1 |p1:---:p0| device0| + * +-------------+ +---------+ +---------+ +--------+ + * when slot0 is calling handle link up + * 1. enum at slot0 to create switch1 and device0, put them in dev_list + * 2. route dev_list to set up route between these two devices + * 3. handle route link up at slot0, add route of left(controller0 & switch0) + * into right(switch1 & device0) and route of right into left + * 4. start switch1 and device0 + */ +static int ubhp_handle_link_up(struct ub_slot *slot) +{ + struct list_head dev_list; + int ret; + + INIT_LIST_HEAD(&dev_list); + + ret = ubhp_enum_at_slot(slot, &dev_list); + if (ret) { + ub_err(slot->uent, "enum at slot%u failed, ret=%d\n", slot->slot_id, ret); + return ret; + } + + if (list_empty(&dev_list)) { + ub_warn(slot->uent, "link up without remote dev\n"); + return -ENXIO; + } + + ret = ub_route_entities(&dev_list); + if (ret) { + ub_err(slot->uent, "hotplug cal route failed, ret=%d\n", ret); + goto err_route; + } + + slot->r_uent = slot->ports->r_uent; + ret = ubhp_update_route_link_up(slot); + if (ret) { + ub_err(slot->uent, "hotplug update route failed, ret=%d\n", ret); + goto err_link_up; + } + + ret = ub_enum_entities_active(&dev_list); + if (ret) { + ub_err(slot->uent, "hotplug start devices failed, ret=%d\n", ret); + goto err_link_up; + } + + return 0; +err_link_up: + ubhp_update_route_link_down(slot); + slot->r_uent = NULL; +err_route: + ub_enum_clear_ent_list(&dev_list); + return ret; +} + +/** + * a simple example for link down + * for a given topo like + * +-------------+ +---------+ +---------+ +--------+ + * | controller0 |p0:---:p0| switch0 |p1:---slot0---:p0| switch1 |p1:---:p0| device0| + * +-------------+ +---------+ +---------+ +--------+ + * when slot0 is calling handle link down + * 1. disconnect slot0 so that p1 and p0 is not connected in software + * 2. put switch1 and device0 into dev_list, mark them as detached + * 3. stop switch1 and device0 + * 4. remove switch1 and device0 + * 5. handle route link down at slot0, del route of right(switch1 & device0) + * from left(controller0 & switch0) + */ +static void ubhp_handle_link_down(struct ub_slot *slot) +{ + struct list_head dev_list; + struct ub_entity *r_uent; + + INIT_LIST_HEAD(&dev_list); + + r_uent = slot->r_uent; + if (!r_uent) { + ub_warn(slot->uent, "link down without remote dev\n"); + return; + } + + ubhp_disconnect_slot(slot); + ubhp_mark_detached_entities(r_uent, &dev_list); + ubhp_stop_entities(&dev_list); + ubhp_remove_entities(&dev_list); + ubhp_update_route_link_down(slot); +} + +static void ubhp_handle_button_press(struct ub_slot *slot) +{ +#define POWER_ON_WAIT 5 + bool queued = false; + + ubhp_get_slot(slot); + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_ON) { + slot->state = SLOT_POWEROFF; + ub_info(slot->uent, "slot%u poweroff\n", slot->slot_id); + ubhp_set_indicators(slot, INDICATOR_BLINKING, INDICATOR_NOOP); + /* for power off, issued the present work immediately */ + queued = queue_delayed_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->present_work, 0); + } else if (slot->state == SLOT_OFF) { + slot->state = SLOT_POWERON; + ub_info(slot->uent, "slot%u poweron\n", slot->slot_id); + ubhp_set_indicators(slot, INDICATOR_BLINKING, INDICATOR_NOOP); + /* for power on, left 5s for possible card insertion */ + queued = queue_delayed_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->present_work, + POWER_ON_WAIT * HZ); + } + + mutex_unlock(&slot->state_lock); + if (!queued) + ubhp_put_slot(slot); +} + +static void ubhp_button_handler(struct work_struct *work) +{ + struct ub_slot *slot = container_of(work, struct ub_slot, button_work); + + ubhp_handle_button_press(slot); + ubhp_put_slot(slot); +} + +void ubhp_handle_power(struct ub_slot *slot, bool power_on) +{ + if (!slot || !PWR(slot)) + return; + + mutex_lock(&slot->state_lock); + + if (slot->state != SLOT_POWERON) + goto out; + + if (power_on) { + ubhp_set_indicators(slot, INDICATOR_ON, INDICATOR_NOOP); + slot->state = SLOT_ON; + ub_info(slot->uent, "slot%u on\n", slot->slot_id); + ub_info(slot->uent, + "slot%u handle hotplug success\n", slot->slot_id); + if (cancel_work(&slot->button_work)) + ubhp_put_slot(slot); + } else { + ubhp_set_slot_power(slot, POWER_OFF); + slot->state = SLOT_OFF; + ubhp_set_indicators(slot, INDICATOR_OFF, INDICATOR_NOOP); + ub_info(slot->uent, "slot%u off\n", slot->slot_id); + ub_info(slot->uent, + "slot%u handle hotplug unsuccess\n", slot->slot_id); + } + +out: + mutex_unlock(&slot->state_lock); +} + +static void ubhp_power_handler(struct work_struct *work) +{ + struct delayed_work *power_work; + struct ub_slot *slot; + + power_work = to_delayed_work(work); + slot = container_of(power_work, struct ub_slot, power_work); + + ubhp_handle_power(slot, false); + ubhp_put_slot(slot); +} + +static void ubhp_handle_present(struct ub_slot *slot) +{ +#define HP_LINK_WAIT_DELAY 10 + mutex_lock(&slot->state_lock); + + if (slot->state == SLOT_POWEROFF || slot->state == SLOT_ON) { + ubhp_handle_link_down(slot); + ubhp_set_slot_power(slot, POWER_OFF); + ubhp_set_indicators(slot, INDICATOR_OFF, INDICATOR_NOOP); + slot->state = SLOT_OFF; + ub_info(slot->uent, "slot%u off\n", slot->slot_id); + goto out; + } + + if (!ubhp_card_present(slot)) + goto clear_state; + + ubhp_set_slot_power(slot, POWER_ON); + + /* If support power ctrl, wait link up process */ + if (PWR(slot)) { + mutex_unlock(&slot->state_lock); + ubhp_get_slot(slot); + queue_delayed_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->power_work, HP_LINK_WAIT_DELAY * HZ); + return; + } + + if (ubhp_handle_link_up(slot)) + goto poweroff; + + ubhp_set_indicators(slot, INDICATOR_ON, INDICATOR_NOOP); + slot->state = SLOT_ON; + ub_info(slot->uent, "slot%u on\n", slot->slot_id); + +out: + /** + * why cancel button work here: + * 1. for a slot with many ports, it's possible that every port will send + * msg when hotplug event occurred, it's possible some of this msgs + * is handled after present work is triggered, if these msgs are + * handled as usually, may come into error like slot power off + * immediately after power on, so button work is banned during + * handling present work + * 2. when the user pressed button repeatedly, some msgs come when + * handling present work, it's reasonable to ignore them + * + * by holding the state_lock both during button work and present work, + * it's guaranteed that button work and present work can't be handled + * at the same time. So cancel button work before present work ends + * makes sure that button work issued during present work is ignored + */ + if (cancel_work(&slot->button_work)) + ubhp_put_slot(slot); + mutex_unlock(&slot->state_lock); + + ub_info(slot->uent, "slot%u handle hotplug succeeded\n", slot->slot_id); + return; +poweroff: + ubhp_set_slot_power(slot, POWER_OFF); +clear_state: + slot->state = SLOT_OFF; + ubhp_set_indicators(slot, INDICATOR_OFF, INDICATOR_NOOP); + ub_info(slot->uent, "slot%u off\n", slot->slot_id); + mutex_unlock(&slot->state_lock); + ub_info(slot->uent, "slot%u handle hotplug failed\n", slot->slot_id); +} + +static void ubhp_present_handler(struct work_struct *work) +{ + struct delayed_work *present_work; + struct ub_slot *slot; + + present_work = to_delayed_work(work); + slot = container_of(present_work, struct ub_slot, present_work); + + ubhp_handle_present(slot); + ubhp_put_slot(slot); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug_ctrl.c b/drivers/ub/ubus/services/hotplug/hotplug_ctrl.c new file mode 100644 index 0000000000000000000000000000000000000000..73d32079bdd81ace11fdc2e883aac5c2e8f6f10b --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_ctrl.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include "../../ubus.h" +#include "../../port.h" +#include "hotplug.h" + +/** + * arrange of SHP cap + * + * +-------------+---------------+---------------+ + * | reg(s) name | start address | end address | + * | slice header| 0x00 | 0x00 | + * | slot number | 0x01 | 0x01 | + * | slot1 regs | 0x02 | 0x0F | + * | slot2 regs | 0x12 | 0x1F | + * ... + * | slotN regs | 0x2 + 10(N-1) | 0xF + 10(N-1) | + */ + +/** + * for any given reg in slot regs of slotN, it's pos can be get by: + * SHP cap pos + 10(N-1) + reg pos in slot regs + * the fronter two elements is represented as following UB_SLOT_BASE marco + */ +#define UB_SLOT_BASE(slot) (UB_SLOT_START + (slot)->slot_id * UB_SLOT_POS) + +static void ub_slot_read_byte(struct ub_slot *slot, u32 pos, u8 *val) +{ + int ret = ub_cfg_read_byte(slot->uent, UB_SLOT_BASE(slot) + pos, val); + + if (unlikely(ret)) { + ub_err(slot->uent, "ub slot%u read %#x failed, ret=%d\n", + slot->slot_id, pos, ret); + *val = 0; + } +} + +int ub_slot_read_dword(struct ub_slot *slot, u32 pos, u32 *val) +{ + int ret = ub_cfg_read_dword(slot->uent, UB_SLOT_BASE(slot) + pos, val); + + if (unlikely(ret)) + ub_err(slot->uent, "ub slot%u read %#x failed, ret=%d\n", + slot->slot_id, pos, ret); + + return ret; +} + +static void ub_slot_write_byte(struct ub_slot *slot, u32 pos, u8 val) +{ + int ret = ub_cfg_write_byte(slot->uent, UB_SLOT_BASE(slot) + pos, val); + + if (unlikely(ret)) + ub_err(slot->uent, "ub slot%u write %#x failed, ret=%d\n", + slot->slot_id, pos, ret); +} + +void ubhp_set_indicators(struct ub_slot *slot, u8 power, u8 work) +{ + u8 val; + + /** + * power state indicator: + * OFF: slot is power off, card can be inserted or removed + * ON: slot is power on, card can't be inserted or removed + * BLINKING: slot is under operation performed by user, + * card can't be inserted or removed + */ + if (power >= INDICATOR_NOOP || !PWR_LED(slot)) + goto set_wl; + + ub_slot_read_byte(slot, UB_SLOT_PL_CTRL, &val); + val &= ~UB_SLOT_PL_CTRL_MASK; + val |= power; + ub_slot_write_byte(slot, UB_SLOT_PL_CTRL, val); + +set_wl: + /** + * card work state indicator: + * OFF: card is running normally + * ON: card is out of service or faulty. + * BLINKING: card is under operation performed by user + */ + if (work >= INDICATOR_NOOP || !WORK_LED(slot)) + return; + + ub_slot_read_byte(slot, UB_SLOT_WL_CTRL, &val); + val &= ~UB_SLOT_WL_CTRL_MASK; + val |= work; + ub_slot_write_byte(slot, UB_SLOT_WL_CTRL, val); +} + +void ubhp_set_slot_power(struct ub_slot *slot, enum power_state power) +{ + if (PWR(slot)) + ub_slot_write_byte(slot, UB_SLOT_PW_CTRL, power); +} + +bool ubhp_card_present(struct ub_slot *slot) +{ + u8 val; + + /* always present if no present ctrl */ + if (!PRESENT(slot)) + return true; + + ub_slot_read_byte(slot, UB_SLOT_PD_STA, &val); + + return !!(val & UB_SLOT_PD_STA_MASK); +} + +bool ubhp_wait_linkup(struct ub_slot *slot) +{ +#define TOTAL_WAIT_TIME 100 /* wait 100ms for linkup */ + u64 timeout = get_jiffies_64() + msecs_to_jiffies(TOTAL_WAIT_TIME); + struct ub_port *port; + + do { + /* only need one port link up */ + for_each_slot_port(port, slot) + if (ub_port_check_link_up(port)) + return true; + } while (time_is_after_jiffies64(timeout)); + + return false; +} + +/* check config space and clear status regs */ +bool ubhp_confirm_event(struct ub_slot *slot, enum hotplug_event event) +{ + u32 pos, mask; + u8 val; + + switch (event) { + case HPE_BUTTON_PRESSED: + pos = UB_SLOT_PP_STA; + mask = UB_SLOT_PP_STA_MASK; + break; + case HPE_PRESENCE_DETECT: + pos = UB_SLOT_PDSC_STA; + mask = UB_SLOT_PDSC_STA_MASK; + break; + default: + return false; + } + + ub_slot_read_byte(slot, pos, &val); + if (!(val & mask)) { + ub_err(slot->uent, "confirm hotplug event %d failed\n", event); + return false; + } + + val &= ~mask; + ub_slot_write_byte(slot, pos, val); + return true; +} + +static void ubhp_enable(struct ub_slot *slot, u32 pos, u32 mask, bool flag) +{ + u8 val; + + if (!flag) + return; + + ub_slot_read_byte(slot, pos, &val); + val |= mask; + ub_slot_write_byte(slot, pos, val); +} + +static void ubhp_disable(struct ub_slot *slot, u32 pos, u32 mask, bool flag) +{ + u8 val; + + if (!flag) + return; + + ub_slot_read_byte(slot, pos, &val); + val &= ~mask; + ub_slot_write_byte(slot, pos, val); +} + +static void ubhp_start_slot(struct ub_slot *slot) +{ + /* enable PP */ + ubhp_enable(slot, UB_SLOT_PP_CTRL, UB_SLOT_PP_CTRL_MASK, BUTTON(slot)); + /* enable PD */ + ubhp_enable(slot, UB_SLOT_PD_CTRL, UB_SLOT_PD_CTRL_MASK, PRESENT(slot)); + /* enable PDS */ + ubhp_enable(slot, UB_SLOT_PDS_CTRL, UB_SLOT_PDS_CTRL_MASK, PRESENT(slot)); + /* enable MS */ + ubhp_enable(slot, UB_SLOT_MS_CTRL, UB_SLOT_MS_CTRL_MASK, true); +} + +static void ubhp_stop_slot(struct ub_slot *slot) +{ + /* disable MS */ + ubhp_disable(slot, UB_SLOT_MS_CTRL, UB_SLOT_MS_CTRL_MASK, true); + /* disable PDS */ + ubhp_disable(slot, UB_SLOT_PDS_CTRL, UB_SLOT_PDS_CTRL_MASK, PRESENT(slot)); + /* disable PD */ + ubhp_disable(slot, UB_SLOT_PD_CTRL, UB_SLOT_PD_CTRL_MASK, PRESENT(slot)); + /* disable PP */ + ubhp_disable(slot, UB_SLOT_PP_CTRL, UB_SLOT_PP_CTRL_MASK, BUTTON(slot)); +} + +void ubhp_start_slots(struct ub_entity *uent) +{ + struct ub_slot *slot; + + list_for_each_entry(slot, &uent->slot_list, node) + ubhp_start_slot(slot); +} + +void ubhp_stop_slots(struct ub_entity *uent) +{ + struct ub_slot *slot; + + list_for_each_entry(slot, &uent->slot_list, node) + ubhp_stop_slot(slot); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug_msg.c b/drivers/ub/ubus/services/hotplug/hotplug_msg.c new file mode 100644 index 0000000000000000000000000000000000000000..53cbe832c3cb13d60409e1b5018d4fa6eb9c7fe9 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_msg.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hp msg: " fmt + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../link.h" +#include "hotplug.h" + +static struct ub_slot *ub_find_slot(struct ub_entity *uent, u16 slot_id) +{ + struct ub_slot *slot; + + list_for_each_entry(slot, &uent->slot_list, node) + if (slot->slot_id == slot_id) + return slot; + + return NULL; +} + +static struct ub_slot *ubhp_get_slot_from_msg(void *pkt) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct ubhp_msg_payload *payload; + struct ub_slot *slot; + struct ub_entity *uent; + u32 seid; + + seid = eid_gen(header->seid_h, header->seid_l); + uent = ub_get_ent_by_eid(seid); + if (!uent) { + pr_warn("get no device by eid %#05x\n", seid); + return NULL; + } + + payload = (struct ubhp_msg_payload *)header->payload; + slot = ubhp_get_slot(ub_find_slot(uent, payload->slot_id)); + if (!slot) + pr_err("can not find slot with id %u\n", payload->slot_id); + + ub_entity_put(uent); + return slot; +} + +void ubhp_handle_event(struct ub_slot *slot, enum hotplug_event event) +{ + bool queued = false; + u32 flag; + + if (!ubhp_confirm_event(slot, event)) + return; + + /** + * get slot if work is queued + * queue_work: return false if work already exists + * mod_delayed_work: return true if work exists, with its timer modified + */ + if (event == HPE_BUTTON_PRESSED) { + queued = queue_work(get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->button_work); + } else if (event == HPE_PRESENCE_DETECT) { + flag = work_busy(&slot->present_work.work); + if (!(flag & WORK_BUSY_RUNNING)) + queued = !mod_delayed_work( + get_rx_msg_wq(UB_MSG_CODE_LINK), + &slot->present_work, 0); + } + if (queued) + ubhp_get_slot(slot); +} + +void ubhp_event_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + struct ub_slot *slot; + + if (len < UB_HP_MSG_SIZE) { + dev_err(&ubc->dev, "hp msg len[%#x] invalid\n", len); + return; + } + + slot = ubhp_get_slot_from_msg(pkt); + if (!slot) + return; + + ubhp_handle_event(slot, + (enum hotplug_event)header->msgetah.sub_msg_code); + ubhp_put_slot(slot); +} diff --git a/drivers/ub/ubus/services/hotplug/hotplug_route.c b/drivers/ub/ubus/services/hotplug/hotplug_route.c new file mode 100644 index 0000000000000000000000000000000000000000..76341766e3625e09497339af4d1ee0ca3223c974 --- /dev/null +++ b/drivers/ub/ubus/services/hotplug/hotplug_route.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include + +#include "../../ubus.h" +#include "../../ubus_entity.h" +#include "../../ubus_driver.h" +#include "../../port.h" +#include "../../route.h" +#include "hotplug.h" + +int ubhp_update_route_link_up(struct ub_slot *slot) +{ + struct ub_entity *uent = slot->uent, *r_uent = slot->r_uent; + struct ub_port *port, *r_port; + int ret; + + for_each_slot_port(port, slot) { + if (!port->r_uent) + continue; + + r_port = port->r_uent->ports + port->r_index; + ret = ub_route_mod_neighbor(port, r_port); + if (ret) + return ret; + + ret = ub_route_mod_neighbor(r_port, port); + if (ret) + return ret; + } + + /* for uent that can't forward, there's no need to update other uent */ + if (ub_entity_support_forward(uent)) + ub_route_mod_bfs(uent); + + if (ub_entity_support_forward(r_uent)) + ub_route_mod_bfs(r_uent); + + ub_route_sync_all(); + return 0; +} + +void ubhp_update_route_link_down(struct ub_slot *slot) +{ + struct ub_entity *uent = slot->uent; + struct ub_port *port; + + for_each_slot_port(port, slot) + ub_route_clear_port(port); + + /* for uent that can't forward, there's no need to update other uent */ + if (ub_entity_support_forward(uent)) + ub_route_del_bfs(uent); + + ub_route_sync_all(); +} + +/** + * ubhp_mark_detached_entities() - mark devices as detached and put them into dev_list + * @root: a device that needs to be removed + * @dev_list: a list to store the detached devices + * + * this func use bfs to mark devices and put them into dev_list, all devices + * that connected with root will be marked as detached + */ +void ubhp_mark_detached_entities(struct ub_entity *root, struct list_head *dev_list) +{ +#define UBHP_KFIFO_DEPTH SZ_16 + DECLARE_KFIFO(kfifo, struct ub_entity *, UBHP_KFIFO_DEPTH); + struct ub_port *port; + struct ub_entity *uent, *r_uent; + + INIT_KFIFO(kfifo); + ub_entity_assign_priv_flag(root, UB_ENTITY_DETACHED, true); + kfifo_put(&kfifo, root); + + down_write(&ub_bus_sem); + while (kfifo_get(&kfifo, &uent)) { + for_each_uent_port(port, uent) { + if (!port->r_uent) + continue; + + r_uent = port->r_uent; + if (ub_entity_test_priv_flag(r_uent, UB_ENTITY_DETACHED)) + continue; + + ub_entity_assign_priv_flag(r_uent, UB_ENTITY_DETACHED, true); + if (!kfifo_put(&kfifo, r_uent)) + ub_err(r_uent, "hp detached entity kfifo put failed!\n"); + } + + list_del(&uent->node); + list_add_tail(&uent->node, dev_list); + } + up_write(&ub_bus_sem); +} + +/** + * ubhp_stop_entities() - stop devices in dev_list + * @dev_list: a list to store devices + */ +void ubhp_stop_entities(struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry_reverse(uent, dev_list, node) + ub_stop_ent(uent); +} + +/** + * ubhp_remove_entities() - remove devices in dev_list + * @dev_list: a list to store devices + */ +void ubhp_remove_entities(struct list_head *dev_list) +{ + struct ub_entity *uent, *tmp; + + list_for_each_entry_safe_reverse(uent, tmp, dev_list, node) + ub_remove_ent(uent); +} diff --git a/drivers/ub/ubus/services/ras.c b/drivers/ub/ubus/services/ras.c new file mode 100644 index 0000000000000000000000000000000000000000..fc4365f819a380459a6036c7efa8efdb0a1ef3f7 --- /dev/null +++ b/drivers/ub/ubus/services/ras.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus ras: " fmt + +#include +#include +#include + +#include "../ubus.h" +#include "../msg.h" +#include "../reset.h" +#include "ras.h" + +#define RAS_CE 0 +#define RAS_UC_NFE 1 +#define RAS_UC_FE 2 +#define RAS_RECOVER_RING_SIZE 16 +#define RAS_SEVERITY_STR(severity) \ + ((severity) == RAS_CE ? "CORRECTABLE" : \ + (severity) == RAS_UC_NFE ? "UNCORRECTABLE NONFATAL" : \ + (severity) == RAS_UC_FE ? "UNCORRECTABLE FATAL" : \ + "UNKNOWN") + +enum ras_err_level { + RAS_ERR_PORT_LEVEL, + RAS_ERR_DEVICE_LEVEL, +}; + +static int cper_severity_to_ub_ras(int cper_severity) +{ + switch (cper_severity) { + case CPER_SEV_FATAL: + return RAS_UC_FE; + case CPER_SEV_RECOVERABLE: + return RAS_UC_NFE; + default: + return RAS_CE; + } +} + +static void print_ub_err_info(struct ub_entity *uent, u32 *spec_info, u32 *vendor_info) +{ +#define DW_EACH_ROUND 4 +#define DW_0 0 +#define DW_1 1 +#define DW_2 2 +#define DW_3 3 + for (int i = 0; i < RAS_SPEC_ERR_PKG_LEN; i += DW_EACH_ROUND) + ub_err(uent, "SPEC Err Pkg Header=%08x %08x %08x %08x\n", + spec_info[i], spec_info[i + DW_1], + spec_info[i + DW_2], spec_info[i + DW_3]); + + ub_err(uent, "VENDOR Err Pkg Header=%08x %08x\n", vendor_info[DW_0], vendor_info[DW_1]); +} + +static void cper_print_ub_err(struct ub_entity *uent, struct ras_recover_entry *entry) +{ + struct ras_cap_regs *reg = entry->regs; + + ub_err(uent, "UBUS ERR type=%s.\n", RAS_SEVERITY_STR(entry->severity)); + + if (entry->err_level == RAS_ERR_PORT_LEVEL) + ub_err(uent, "Error level: PORT ERR, port=%u.\n", entry->port); + else + ub_err(uent, "Error level: DEVICE ERR.\n"); + + if (!reg) { + ub_err(uent, "No error detail need to show.\n"); + return; + } + + ub_err(uent, "Uncorrectable ERR status=%#08llx, mask=%#08llx, severity=%#08llx\n", + reg->uncor_status, reg->uncor_mask, reg->uncor_severity); + ub_err(uent, "Correctable ERR status=%#08llx, mask=%#08llx, severity=%#08llx\n", + reg->cor_status, reg->cor_mask, reg->cor_severity); + + print_ub_err_info(uent, reg->spec_def_err_info, reg->vendor_def_err_info); +} + +static ub_ers_result_t ras_merge_result(ub_ers_result_t orig, + ub_ers_result_t new_one) +{ + ub_ers_result_t ret = orig; + + if (new_one == UB_ERS_RESULT_NO_ERR_DRIVER) + return UB_ERS_RESULT_NO_ERR_DRIVER; + + if (new_one == UB_ERS_RESULT_NONE) + return ret; + + switch (orig) { + case UB_ERS_RESULT_CAN_RECOVER: + case UB_ERS_RESULT_RECOVERED: + ret = new_one; + break; + case UB_ERS_RESULT_DISCONNECT: + if (new_one == UB_ERS_RESULT_NEED_RESET) + ret = new_one; + break; + default: + pr_err("unused ras error result %d\n", new_one); + break; + } + + return ret; +} + +static ub_ers_result_t report_resource_enabled(struct ub_entity *uent, + ub_ers_result_t orig) +{ + ub_ers_result_t result, new_one; + struct ub_driver *udrv; + + device_lock(&uent->dev); + udrv = uent->driver; + if (!udrv || !udrv->err_handler || !udrv->err_handler->ub_resource_enabled) { + result = orig; + goto out; + } + + new_one = udrv->err_handler->ub_resource_enabled(uent); + result = ras_merge_result(orig, new_one); +out: + device_unlock(&uent->dev); + return result; +} + +static void ub_uevent_ers(struct ub_entity *uent, ub_ers_result_t err_type) +{ +#define MAX_ENVP 3 + char *envp[MAX_ENVP]; + int idx = 0; + + switch (err_type) { + case UB_ERS_RESULT_NONE: + case UB_ERS_RESULT_CAN_RECOVER: + envp[idx++] = "ERROR_EVENT=BEGIN_RECOVERY"; + envp[idx++] = "DEVICE_ONLINE=0"; + break; + case UB_ERS_RESULT_RECOVERED: + envp[idx++] = "ERROR_EVENT=SUCCESSFUL_RECOVERY"; + envp[idx++] = "DEVICE_ONLINE=1"; + break; + case UB_ERS_RESULT_DISCONNECT: + envp[idx++] = "ERROR_EVENT=FAILED_RECOVERY"; + envp[idx++] = "DEVICE_ONLINE=0"; + break; + default: + pr_err("unused uevent ras err_type %d\n", err_type); + break; + } + + if (idx > 0) { + envp[idx++] = NULL; + kobject_uevent_env(&uent->dev.kobj, KOBJ_CHANGE, envp); + } +} + +static ub_ers_result_t ub_error_report(struct ub_entity *uent, ub_channel_state_t state) +{ + struct ub_driver *udrv; + ub_ers_result_t result; + + device_lock(&uent->dev); + udrv = uent->driver; + if (!udrv || !udrv->err_handler || !udrv->err_handler->ub_error_detected) { + ub_warn(uent, "No driver bounded or no handler provided.\n"); + result = UB_ERS_RESULT_NO_ERR_DRIVER; + goto exit; + } + + result = udrv->err_handler->ub_error_detected(uent, state); + ub_uevent_ers(uent, result); + result = ras_merge_result(UB_ERS_RESULT_CAN_RECOVER, result); +exit: + device_unlock(&uent->dev); + return result; +} + +static void ub_do_recovery(struct ub_entity *uent, ub_channel_state_t state, + struct ras_recover_entry *entry) +{ + u32 port_id = entry->port; + ub_ers_result_t ret; + int recovery; + + ret = ub_error_report(uent, state); + if (ret == UB_ERS_RESULT_NEED_RESET) { + if (entry->err_level == RAS_ERR_PORT_LEVEL) + recovery = ub_port_reset(uent, port_id); + else + recovery = is_ibus_controller(uent) ? 0 : ub_device_reset(uent); + + if (!recovery) + ret = UB_ERS_RESULT_RECOVERED; + else + goto failed; + } + + if (ret == UB_ERS_RESULT_CAN_RECOVER) + ret = report_resource_enabled(uent, UB_ERS_RESULT_RECOVERED); + + if (ret != UB_ERS_RESULT_RECOVERED) + goto failed; + + ub_info(uent, "uent ras recovery succeeded\n"); + return; +failed: + ub_uevent_ers(uent, UB_ERS_RESULT_DISCONNECT); + ub_err(uent, "uent ras recovery failed\n"); +} + +static inline void ras_cap_regs_free(struct ras_cap_regs **regs) +{ + if (!regs || !(*regs)) + return; + + kfree(*regs); + *regs = NULL; +} + +static DEFINE_KFIFO(ras_recover_ring, struct ras_recover_entry, + RAS_RECOVER_RING_SIZE); + +static void ub_ras_recover_work_func(struct work_struct *work) +{ + struct ras_recover_entry entry; + struct ub_entity *uent; + + while (kfifo_get(&ras_recover_ring, &entry)) { + uent = ub_get_ent_by_eid(entry.eid); + if (!uent) { + pr_err("can not find ub entity by eid=%#05x\n", + entry.eid); + ras_cap_regs_free(&entry.regs); + continue; + } + + cper_print_ub_err(uent, &entry); + if (uent->ubc->cluster) { + ub_err(uent, "UB RAS: no need to recover with cluster mode\n"); + } else { + if (entry.severity == RAS_UC_FE) + ub_do_recovery(uent, ub_channel_io_frozen, &entry); + else if (entry.severity == RAS_UC_NFE) + ub_do_recovery(uent, ub_channel_io_normal, &entry); + } + + ras_cap_regs_free(&entry.regs); + ub_entity_put(uent); + } +} + +static inline void ras_recover_entry_init(struct ras_recover_entry *entry, + struct cper_sec_ubus *ubus_err) +{ + entry->eid = ubus_err->eid; + entry->cna = ubus_err->cna; + entry->port = ubus_err->port; + entry->vendor = ubus_err->vendor; + entry->device = ubus_err->device; + entry->type = ubus_err->type; + entry->class_code = ubus_err->class_code; +} + +static DEFINE_SPINLOCK(ub_ras_recover_ring_lock); +static DECLARE_WORK(ub_ras_recover_work, ub_ras_recover_work_func); + +static void ub_ras_recover_queue(struct cper_sec_ubus *ubus_err, int severity) +{ +#define PORT_VALID_BIT 0b100ULL +#define OVERFLOW_FLAG_BIT 0b10000ULL + int ub_severity = cper_severity_to_ub_ras(severity); + struct ras_cap_regs *ras_cap, *tmp_cap; + struct ras_recover_entry entry = {}; + u32 overflow = 0; + u32 err_status; + u32 err_level; + + if (ubus_err->validation_bits & OVERFLOW_FLAG_BIT) { + overflow = ubus_err->overflow_flag; + pr_info("severity %s is overflowed, flag=%#x\n", + RAS_SEVERITY_STR(ub_severity), overflow); + } + + tmp_cap = (struct ras_cap_regs *)ubus_err->err_info; + err_status = (ub_severity == RAS_CE) ? + tmp_cap->cor_status : tmp_cap->uncor_status; + if (err_status == 0 && overflow == 0) { + pr_info("no need to handle ras with err_status=0x%x, severity=%s\n", + err_status, RAS_SEVERITY_STR(ub_severity)); + return; + } + + ras_cap = kzalloc(sizeof(*ras_cap), GFP_ATOMIC); + if (!ras_cap) + return; + + memcpy(ras_cap, tmp_cap, sizeof(struct ras_cap_regs)); + err_level = (ubus_err->validation_bits & PORT_VALID_BIT) ? + RAS_ERR_PORT_LEVEL : RAS_ERR_DEVICE_LEVEL; + ras_recover_entry_init(&entry, ubus_err); + entry.err_level = err_level; + entry.severity = ub_severity; + entry.overflow = overflow; + entry.regs = ras_cap; + + if (kfifo_in_spinlocked(&ras_recover_ring, &entry, 1, + &ub_ras_recover_ring_lock)) { + schedule_work(&ub_ras_recover_work); + return; + } + + if (ub_severity == RAS_UC_FE) + pr_err("FIFO ring overflow when recovering error for eid:%05x, severity:%s\n", + ubus_err->eid, RAS_SEVERITY_STR(ub_severity)); + else + pr_err("FIFO ring overflow when recovering error for eid:%05x, port:%u, severity:%s\n", + ubus_err->eid, ubus_err->port, + RAS_SEVERITY_STR(ub_severity)); + + kfree(ras_cap); +} + +void ub_ras_init(void) +{ + ub_ras_register_recover_func(ub_ras_recover_queue); +} + +void ub_ras_uninit(void) +{ + ub_ras_register_recover_func(NULL); + (void)flush_work(&ub_ras_recover_work); +} diff --git a/drivers/ub/ubus/services/ras.h b/drivers/ub/ubus/services/ras.h new file mode 100644 index 0000000000000000000000000000000000000000..c68c2dce8471be61ca87b8c51ea9fc00f5f39e4f --- /dev/null +++ b/drivers/ub/ubus/services/ras.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __SERVICE_RAS_H__ +#define __SERVICE_RAS_H__ + +#define RAS_SPEC_ERR_PKG_LEN 32 + +#pragma pack(4) +struct ras_cap_regs { + u32 err_ctrl_reg; + u64 uncor_status; + u64 uncor_mask; + u64 uncor_severity; + u64 cor_status; + u64 cor_mask; + u64 cor_severity; + u32 err_info_ctrl_reg; + u32 spec_def_err_info[RAS_SPEC_ERR_PKG_LEN]; + u32 vendor_def_err_info[2]; +}; +#pragma pack() + +struct ras_recover_entry { + u32 eid; + u32 cna; + u32 port; + u32 vendor; + u16 device; + u16 type; + u16 class_code; + u16 err_level; + int severity; + u32 overflow; + struct ras_cap_regs *regs; +}; + +#endif /* __SERVICE_RAS_H__ */ diff --git a/drivers/ub/ubus/services/service.c b/drivers/ub/ubus/services/service.c new file mode 100644 index 0000000000000000000000000000000000000000..b925da25cec7987cfa81b50cffc8da98b252c067 --- /dev/null +++ b/drivers/ub/ubus/services/service.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus service: " fmt + +#include "../ubus.h" +#include "../ubus_driver.h" +#include "service.h" + +static int ub_service_probe(struct device *dev) +{ + struct ub_service_device *sdev; + struct ub_service_driver *sdrv; + int status; + + if (!dev || !dev->driver) + return -ENODEV; + + sdrv = to_ub_service_driver(dev->driver); + if (!sdrv || !sdrv->probe) + return -ENODEV; + + sdev = to_ub_service_device(dev); + status = sdrv->probe(sdev); + if (status) + return status; + + get_device(dev); + return 0; +} + +static int ub_service_remove(struct device *dev) +{ + struct ub_service_device *sdev; + struct ub_service_driver *sdrv; + + if (!dev || !dev->driver) + return 0; + + sdev = to_ub_service_device(dev); + sdrv = to_ub_service_driver(dev->driver); + if (sdrv && sdrv->remove) { + sdrv->remove(sdev); + put_device(dev); + } + + return 0; +} + +static void ub_service_shutdown(struct device *dev) +{ +} + +int ub_service_driver_register(struct ub_service_driver *drv) +{ + drv->driver.name = drv->name; + drv->driver.bus = &ub_service_bus_type; + drv->driver.probe = ub_service_probe; + drv->driver.remove = ub_service_remove; + drv->driver.shutdown = ub_service_shutdown; + + return driver_register(&drv->driver); +} + +void ub_service_driver_unregister(struct ub_service_driver *drv) +{ + driver_unregister(&drv->driver); +} diff --git a/drivers/ub/ubus/services/service.h b/drivers/ub/ubus/services/service.h new file mode 100644 index 0000000000000000000000000000000000000000..85ac7654f912dc47d7eabcc11c2ae7298fe849e3 --- /dev/null +++ b/drivers/ub/ubus/services/service.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __SERVICES_SERVICE_H__ +#define __SERVICES_SERVICE_H__ + +#define UB_SERVICE_HP_SHIFT 1 +#define UB_SERVICE_HP (1 << UB_SERVICE_HP_SHIFT) + +#define UB_MAXSERIVCES 3 + +struct ub_service_device { + int irq; + struct ub_entity *uent; + u32 service; + void *priv_data; + struct device device; +}; + +struct ub_service_driver { + const char *name; + int (*probe)(struct ub_service_device *dev); + void (*remove)(struct ub_service_device *dev); + + u32 service; + + struct device_driver driver; +}; + +#define to_ub_service_device(d) \ + container_of(d, struct ub_service_device, device) +#define to_ub_service_driver(d) \ + container_of(d, struct ub_service_driver, driver) + +void ub_ras_init(void); +void ub_ras_uninit(void); +void ubhp_service_init(void); +void ubhp_service_uninit(void); + +int ub_service_driver_register(struct ub_service_driver *drv); +void ub_service_driver_unregister(struct ub_service_driver *drv); + +#endif /* __SERVICES_SERVICE_H__ */ diff --git a/drivers/ub/ubus/sysfs.c b/drivers/ub/ubus/sysfs.c new file mode 100644 index 0000000000000000000000000000000000000000..75e7b6c5a4c12aa388371d4b9d623c16a8f5f092 --- /dev/null +++ b/drivers/ub/ubus/sysfs.c @@ -0,0 +1,945 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus sysfs: " fmt + +#include "ubus.h" +#include "ubus_entity.h" +#include "instance.h" +#include "port.h" +#include "resource.h" +#include "ubus_driver.h" +#include "sysfs.h" + +static inline void ub_resource_to_user(const struct ub_entity *dev, int res_id, + const struct resource *rsrc, + resource_size_t *start, + resource_size_t *end) +{ + *start = rsrc->start; + *end = rsrc->end; +} + +#define ub_config_attr(field, format_string) \ +static ssize_t field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct ub_entity *uent; \ + \ + uent = to_ub_entity(dev); \ + return sysfs_emit(buf, format_string, uent->guid.bits.field); \ +} \ +static DEVICE_ATTR_RO(field) + +ub_config_attr(device, "%#06x\n"); +ub_config_attr(type, "%#x\n"); +ub_config_attr(vendor, "%#06x\n"); + +static ssize_t ubc_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%#05x\n", uent->ubc->uent->uent_num); +} +static DEVICE_ATTR_RO(ubc); + +static ssize_t class_code_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%#06x\n", uent->class_code); +} +static DEVICE_ATTR_RO(class_code); + +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + int count; + + count = ub_show_guid(&uent->guid, buf); + + return count + sysfs_emit_at(buf, count, "\n"); +} +static DEVICE_ATTR_RO(guid); + +static ssize_t entity_idx_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%u\n", uent->entity_idx); +} +static DEVICE_ATTR_RO(entity_idx); + +static ssize_t eid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%u\n", uent->eid); +} +static DEVICE_ATTR_RO(eid); + +static ssize_t tid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%u\n", uent->tid); +} +static DEVICE_ATTR_RO(tid); + +static ssize_t kref_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", kref_read(&dev->kobj.kref)); +} +static DEVICE_ATTR_RO(kref); + +#define DATA_POS (cur_pos - usr_pos) +#define REMAIN_BYTE (len - DATA_POS) + +static ssize_t ub_read_config(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t usr_pos, size_t count) +{ + struct ub_entity *uent = to_ub_entity(kobj_to_dev(kobj)); + u64 cur_pos = usr_pos; + u8 *data = (u8 *)buf; + size_t len = count >> 1; + u32 val; + + if (count == PAGE_SIZE || len == 0) + return -EINVAL; + + memset(buf, 0, count); + + if (cur_pos & 1) { + if (ub_cfg_read_byte(uent, cur_pos, (u8 *)&val)) + memcpy(data + DATA_POS + len, &val, 1); + memcpy(data + DATA_POS, &val, 1); + cur_pos++; + } + + if ((cur_pos & SZ_2) && (REMAIN_BYTE >= SZ_2)) { + if (ub_cfg_read_word(uent, cur_pos, (u16 *)&val)) + memcpy(data + DATA_POS + len, &val, SZ_2); + memcpy(data + DATA_POS, &val, SZ_2); + cur_pos += SZ_2; + } + + while (REMAIN_BYTE >= SZ_4) { + if (ub_cfg_read_dword(uent, cur_pos, &val)) + memcpy(data + DATA_POS + len, &val, SZ_4); + memcpy(data + DATA_POS, &val, SZ_4); + cur_pos += SZ_4; + } + + if (REMAIN_BYTE >= SZ_2) { + if (ub_cfg_read_word(uent, cur_pos, (u16 *)&val)) + memcpy(data + DATA_POS + len, &val, SZ_2); + memcpy(data + DATA_POS, &val, SZ_2); + cur_pos += SZ_2; + } + + if (REMAIN_BYTE) { + if (ub_cfg_read_byte(uent, cur_pos, (u8 *)&val)) + memcpy(data + DATA_POS + len, &val, 1); + memcpy(data + DATA_POS, &val, 1); + cur_pos++; + } + + return count; +} + +static ssize_t ub_write_config(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t usr_pos, size_t count) +{ + struct ub_entity *uent = to_ub_entity(kobj_to_dev(kobj)); + u64 cur_pos = usr_pos; + u8 *data = (u8 *)buf; + size_t len = count; + + if (cur_pos & 1) { + u8 val; + + memcpy(&val, data + DATA_POS, 1); + ub_cfg_write_byte(uent, cur_pos, val); + cur_pos++; + } + + if ((cur_pos & SZ_2) && (REMAIN_BYTE >= SZ_2)) { + u16 val; + + memcpy(&val, data + DATA_POS, SZ_2); + ub_cfg_write_word(uent, cur_pos, val); + cur_pos += SZ_2; + } + + while (REMAIN_BYTE >= SZ_4) { + u32 val; + + memcpy(&val, data + DATA_POS, SZ_4); + ub_cfg_write_dword(uent, cur_pos, val); + cur_pos += SZ_4; + } + + if (REMAIN_BYTE >= SZ_2) { + u16 val; + + memcpy(&val, data + DATA_POS, SZ_2); + ub_cfg_write_word(uent, cur_pos, val); + cur_pos += SZ_2; + } + + if (REMAIN_BYTE) { + u8 val; + + memcpy(&val, data + DATA_POS, 1); + ub_cfg_write_byte(uent, cur_pos, val); + cur_pos++; + } + + return count; +} + +#undef REMAIN_BYTE +#undef DATA_POS + +static const struct bin_attribute ub_config_bin_attr = { + .attr = { + .name = "config", + .mode = 0644, + }, + .size = UB_CFG_SAPCE_SLICE_END, + .read = ub_read_config, + .write = ub_write_config, +}; + +static ssize_t resource_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + resource_size_t start, end; + int i, cnt = 0; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + struct resource *res = &uent->zone[i].res; + + ub_resource_to_user(uent, i, res, &start, &end); + cnt += sysfs_emit_at(buf, cnt, "%#016llx %#016llx %#016llx\n", + (unsigned long long)start, + (unsigned long long)end, + (unsigned long long)res->flags); + } + return cnt; +} +static DEVICE_ATTR_RO(resource); + +static ssize_t driver_override_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent = to_ub_entity(dev); + int ret; + + ret = driver_set_override(dev, &uent->driver_override, buf, count); + if (ret) + return ret; + + return count; +} + +static ssize_t driver_override_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + ssize_t len; + + device_lock(dev); + len = sysfs_emit(buf, "%s\n", uent->driver_override); + device_unlock(dev); + return len; +} +static DEVICE_ATTR_RW(driver_override); + +static ssize_t match_driver_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent = to_ub_entity(dev); + unsigned long val; + ssize_t result; + + result = kstrtoul(buf, 0, &val); + if (result < 0) + return result; + if (!(val == 0 || val == 1)) + return -EINVAL; + + device_lock(dev); + uent->match_driver = !!val; + device_unlock(dev); + + return count; +} + +static ssize_t match_driver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + ssize_t len; + + device_lock(dev); + len = sysfs_emit(buf, "%s\n", uent->match_driver ? "true" : "false"); + device_unlock(dev); + + return len; +} +static DEVICE_ATTR_RW(match_driver); + +static ssize_t direct_link_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + struct ub_port *port; + int cnt = 0; + + for_each_uent_port(port, uent) { + if (!port->r_uent) + continue; + cnt += sysfs_emit_at(buf, cnt, "%#04x : %#04x [%#05x]\n", + port->index, port->r_index, + port->r_uent->uent_num); + } + + return cnt; +} +DEVICE_ATTR_RO(direct_link); + +static ssize_t primary_cna_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#06x\n", uent->cna); +} +DEVICE_ATTR_RO(primary_cna); + +static ssize_t ummu_map_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#04x\n", uent->ubc->attr.ummu_map); +} +DEVICE_ATTR_RO(ummu_map); + +static ssize_t instance_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + u32 eid = 0; + + if (uent->bi) + eid = uent->bi->info.eid; + + return sysfs_emit(buf, "%#05x\n", eid); +} +DEVICE_ATTR_RO(instance); + +static ssize_t upi_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#04x\n", uent->upi); +} +DEVICE_ATTR_RO(upi); + +static ssize_t numa_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + return sysfs_emit(buf, "%#04x\n", uent->ubc->attr.proximity_domain); +} +DEVICE_ATTR_RO(numa); + +static ssize_t primary_entity_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + + if (uent->entity_idx == 0) /* this device is primary entity */ + goto pue_print; + else if (uent->is_mue) /* find primary entity for mue */ + uent = uent->pue; + else /* find primary entity for ue */ + uent = uent->pue->pue; + +pue_print: + return sysfs_emit(buf, "%#05x\n", uent->uent_num); +} +DEVICE_ATTR_RO(primary_entity); + +static struct attribute *ub_entity_attrs[] = { + &dev_attr_resource.attr, + &dev_attr_vendor.attr, + &dev_attr_device.attr, + &dev_attr_class_code.attr, + &dev_attr_type.attr, + &dev_attr_driver_override.attr, + &dev_attr_match_driver.attr, + &dev_attr_direct_link.attr, + &dev_attr_guid.attr, + &dev_attr_ubc.attr, + &dev_attr_primary_cna.attr, + &dev_attr_instance.attr, + &dev_attr_upi.attr, + &dev_attr_entity_idx.attr, + &dev_attr_numa.attr, + &dev_attr_eid.attr, + &dev_attr_tid.attr, + &dev_attr_primary_entity.attr, + &dev_attr_kref.attr, + &dev_attr_ummu_map.attr, + NULL +}; + +static const struct attribute_group ub_entity_group = { + .attrs = ub_entity_attrs, +}; + +const struct attribute_group *ub_entity_groups[] = { + &ub_entity_group, + NULL +}; + +static ssize_t cluster_show(const struct bus_type *bus, char *buf) +{ + struct ub_bus_controller *ubc; + + if (list_empty(&ubc_list)) + return 0; + + ubc = list_first_entry(&ubc_list, struct ub_bus_controller, node); + return sysfs_emit(buf, "%d\n", ubc->cluster); +} +static BUS_ATTR_RO(cluster); + + +static struct attribute *ub_bus_attrs[] = { + &bus_attr_instance.attr, + &bus_attr_cluster.attr, + NULL +}; + +static const struct attribute_group ub_bus_group = { + .attrs = ub_bus_attrs, +}; + +const struct attribute_group *ub_bus_groups[] = { + &ub_bus_group, + NULL +}; + +const struct device_type ub_dev_type = { + .groups = NULL, +}; + +static ssize_t reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent = to_ub_entity(dev); + unsigned long val; + ssize_t result; + + result = kstrtoul(buf, 0, &val); + if (result < 0) + return result; + + if (val != 1) + return -EINVAL; + + result = ub_reset_entity(uent); + if (result < 0) + return result; + + return count; +} +static DEVICE_ATTR_WO(reset); + +static ssize_t device_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent = to_ub_entity(dev); + unsigned long val; + ssize_t result; + + result = kstrtoul(buf, 0, &val); + if (result < 0 || val != 1) + return -EINVAL; + + result = ub_device_reset(uent); + if (result < 0) + return result; + + return count; +} +static DEVICE_ATTR_WO(device_reset); + +static ssize_t sw_cap_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%d\n", uent->sw_cap); +} +static DEVICE_ATTR_RO(sw_cap); + +static ssize_t ub_total_entities_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%u\n", uent->total_funcs); +} +static DEVICE_ATTR_RO(ub_total_entities); + +static ssize_t ub_totalues_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%u\n", uent->total_ues); +} +static DEVICE_ATTR_RO(ub_totalues); + +static ssize_t ub_numues_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ub_entity *uent; + + uent = to_ub_entity(dev); + return sysfs_emit(buf, "%u\n", uent->num_ues); +} + +static int ub_numues_store_driver_check(struct ub_entity *uent) +{ + if (!uent->driver) { + ub_err(uent, "no driver bound to device, enable failed\n"); + return -ENOENT; + } + + if (!uent->driver->virt_configure) { + ub_err(uent, "driver does not support virt_configure via sysfs\n"); + return -ENOENT; + } + + return 0; +} + +static void ub_numues_store_ues_process(struct ub_entity *uent, int nums) +{ + int ue_start_idx, ue_end_idx, i, cnt, ret; + struct ub_entity *ue; + + /* + * The software finds "cnt" disabled ues for "ue_start_idx" to + * "ue_end_idx" based on the ordered ue_list. "ue_start_idx" is + * the entity_idx of first ues and "ue_end_idx" is calculated based + * on the total number(that is, "nums") ues to be enabled. + */ + ue_start_idx = uent->uem.start_entity_idx; + ue_end_idx = ue_start_idx + nums - 1; + i = ue_start_idx; + cnt = nums - uent->num_ues; + list_for_each_entry(ue, &uent->ue_list, node) { + for (; i < ue->entity_idx; i++) { + /* + * The "ue_list" is sorted by entity_idx in ascending order. + * Ensure that others ues before this ue are enabled. + */ + ret = uent->driver->virt_configure(uent, i, 1); + if (ret) + ub_warn(uent, "driver virt_configure, ret=%d\n", ret); + if (--cnt == 0) + return; + } + /* Skip this enabled ue. */ + i++; + } + + /* Ensure that the remaining ues enabled. */ + for (; i <= ue_end_idx; i++) { + ret = uent->driver->virt_configure(uent, i, 1); + if (ret) + ub_warn(uent, "driver virt_configure, ret=%d\n", ret); + if (--cnt == 0) + return; + } +} + +static ssize_t ub_numues_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent, *ue, *tmp; + int nums, ret; + + uent = to_ub_entity(dev); + + ret = kstrtoint(buf, 0, &nums); + if (ret < 0) + return ret; + + ub_info(uent, "enable %d ue, total_ue=%u\n", + nums, uent->total_ues); + + if (nums < 0 || nums > uent->total_ues) + return -EINVAL; + + device_lock(&uent->dev); + ret = ub_numues_store_driver_check(uent); + if (ret) + goto exit; + + if (nums == 0) { + list_for_each_entry_safe_reverse(ue, tmp, &uent->ue_list, node) + (void)uent->driver->virt_configure(uent, ue->entity_idx, 0); + goto exit; + } + + if (nums <= uent->num_ues || (!entity_flex_en && uent->num_ues)) { + ub_info(uent, "ue already enabled, num=%u.\n", uent->num_ues); + goto exit; + } + + ub_numues_store_ues_process(uent, nums); +exit: + device_unlock(&uent->dev); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(ub_numues); + +static ssize_t ub_release_ue_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ub_entity *uent, *ue, *tmp; + unsigned long uent_num; + int ret; + +#define BASE_16 16 + ret = kstrtoul(buf, BASE_16, &uent_num); + if (ret < 0) + return ret; + + uent = to_ub_entity(dev); + device_lock(&uent->dev); + ret = ub_numues_store_driver_check(uent); + if (ret) + goto exit; + + list_for_each_entry_safe_reverse(ue, tmp, &uent->ue_list, node) + if (ue->uent_num == (u32)uent_num) { + (void)uent->driver->virt_configure(uent, ue->entity_idx, 0); + device_unlock(&uent->dev); + return count; + } + + ub_err(uent, "uent %#05lx does not belong to this pue\n", uent_num); +exit: + device_unlock(&uent->dev); + return -EIO; +} +static DEVICE_ATTR_WO(ub_release_ue); + +struct dev_attr_creat_group { + struct device_attribute *attr; + bool conditions; +}; + +static ssize_t ue_list_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + struct ub_entity *ent; + int cnt = 0; + + down_read(&ub_bus_sem); + list_for_each_entry(ent, &uent->ue_list, node) + cnt += sysfs_emit_at(buf, cnt, "%s\n", dev_name(&ent->dev)); + up_read(&ub_bus_sem); + + return cnt; +} +DEVICE_ATTR_RO(ue_list); + +static ssize_t mue_list_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ub_entity *uent = to_ub_entity(dev); + struct ub_entity *ent; + int cnt = 0; + + down_read(&ub_bus_sem); + list_for_each_entry(ent, &uent->mue_list, node) + cnt += sysfs_emit_at(buf, cnt, "%s\n", dev_name(&ent->dev)); + up_read(&ub_bus_sem); + + return cnt; +} +DEVICE_ATTR_RO(mue_list); + +#define CREATE_CONDITIONS(c) ((c) ? 1 : 0) + +/* When adding sysfs, remember to add it in remove function. */ +static int ub_create_capabilities_sysfs(struct ub_entity *uent) +{ + struct dev_attr_creat_group grp[] = { + { &dev_attr_reset, CREATE_CONDITIONS(uent->reset_fn) }, + { &dev_attr_device_reset, + CREATE_CONDITIONS(uent->entity_idx == 0 && !is_ibus_controller(uent)) }, + { &dev_attr_ub_total_entities, CREATE_CONDITIONS(uent->entity_idx == 0) }, + { &dev_attr_ub_totalues, CREATE_CONDITIONS(uent->is_mue) }, + { &dev_attr_ub_numues, CREATE_CONDITIONS(uent->is_mue && !uent->is_vdm_idev) }, + { &dev_attr_sw_cap, CREATE_CONDITIONS(is_ibus_controller(uent)) }, + { &dev_attr_ub_release_ue, CREATE_CONDITIONS(uent->is_mue && entity_flex_en) }, + { &dev_attr_mue_list, CREATE_CONDITIONS(uent->entity_idx == 0) }, + { &dev_attr_ue_list, CREATE_CONDITIONS(uent->is_mue) }, + }; + int retval, i; + + for (i = 0; i < ARRAY_SIZE(grp); i++) { + if (grp[i].conditions) { + retval = device_create_file(&uent->dev, grp[i].attr); + if (retval) + goto err; + } + } + + return 0; +err: + for (i = i - 1; i >= 0; i--) + if (grp[i].conditions) + device_remove_file(&uent->dev, grp[i].attr); + return retval; +} + +static void ub_remove_capabilities_sysfs(struct ub_entity *uent) +{ + struct dev_attr_creat_group grp[] = { + { &dev_attr_reset, CREATE_CONDITIONS(uent->reset_fn) }, + { &dev_attr_device_reset, + CREATE_CONDITIONS(uent->entity_idx == 0 && !is_ibus_controller(uent)) }, + { &dev_attr_ub_total_entities, CREATE_CONDITIONS(uent->entity_idx == 0) }, + { &dev_attr_ub_totalues, CREATE_CONDITIONS(uent->is_mue) }, + { &dev_attr_ub_numues, CREATE_CONDITIONS(uent->is_mue && !uent->is_vdm_idev) }, + { &dev_attr_sw_cap, CREATE_CONDITIONS(is_ibus_controller(uent)) }, + { &dev_attr_ub_release_ue, CREATE_CONDITIONS(uent->is_mue && entity_flex_en) }, + { &dev_attr_mue_list, CREATE_CONDITIONS(uent->entity_idx == 0) }, + { &dev_attr_ue_list, CREATE_CONDITIONS(uent->is_mue) }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(grp); i++) + if (grp[i].conditions) + device_remove_file(&uent->dev, grp[i].attr); +} + +static bool ub_mmap_fits(struct ub_entity *uent, int idx, + struct vm_area_struct *vma) +{ + resource_size_t len, start, size; + + if (ub_resource_len(uent, idx) == 0) + return false; + + len = vma_pages(vma); + start = vma->vm_pgoff; + size = ((ub_resource_len(uent, idx) - 1) >> PAGE_SHIFT) + 1; + if (start < size && (start + len <= size)) + return true; + + return false; +} + +static int ub_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma, int write_combine) +{ + struct ub_entity *uent = to_ub_entity(kobj_to_dev(kobj)); + unsigned long idx = (unsigned long)attr->private; + + if (idx >= MAX_UB_RES_NUM) + return -EINVAL; + + if (!ub_mmap_fits(uent, idx, vma)) + return -EINVAL; + + return ub_mmap_resource_range(uent, idx, vma, write_combine); +} + +static int ub_mmap_resource_wc(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + return ub_mmap_resource(kobj, attr, vma, 1); +} + +static int ub_mmap_resource_uc(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + return ub_mmap_resource(kobj, attr, vma, 0); +} + +static int ub_create_attr(struct ub_entity *uent, int num, int write_combine) +{ + /* allocate attribute structure, piggyback attribute name */ + int name_len = write_combine ? 13 : 10; + struct bin_attribute *attr; + char *res_attr_name; + int retval; + + attr = kzalloc(sizeof(*attr) + name_len, GFP_ATOMIC); + if (!attr) + return -ENOMEM; + + res_attr_name = (char *)(attr + 1); + + sysfs_bin_attr_init(attr); + if (write_combine) { + uent->res_attr_wc[num] = attr; + sprintf(res_attr_name, "resource%d_wc", num); + attr->mmap = ub_mmap_resource_wc; + } else { + uent->res_attr[num] = attr; + sprintf(res_attr_name, "resource%d", num); + attr->mmap = ub_mmap_resource_uc; + } + attr->attr.name = res_attr_name; + attr->attr.mode = 0600; + attr->size = ub_resource_len(uent, num); + attr->private = (void *)(unsigned long)num; + retval = sysfs_create_bin_file(&uent->dev.kobj, attr); + if (retval) { + kfree(attr); + write_combine ? (uent->res_attr_wc[num] = NULL) : + (uent->res_attr[num] = NULL); + } + + return retval; +} + +static void ub_remove_resource_files(struct ub_entity *uent) +{ + int i; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + struct bin_attribute *res_attr; + + res_attr = uent->res_attr[i]; + if (res_attr) { + sysfs_remove_bin_file(&uent->dev.kobj, res_attr); + kfree(res_attr); + uent->res_attr[i] = NULL; + } + + res_attr = uent->res_attr_wc[i]; + if (res_attr) { + sysfs_remove_bin_file(&uent->dev.kobj, res_attr); + kfree(res_attr); + uent->res_attr_wc[i] = NULL; + } + } +} + +static int ub_create_resource_files(struct ub_entity *uent) +{ + int i; + int retval; + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + /* skip empty resources */ + if (!ub_resource_len(uent, i)) + continue; + + retval = ub_create_attr(uent, i, 0); + if (retval) { + ub_remove_resource_files(uent); + return retval; + } + + retval = ub_create_attr(uent, i, 1); + if (retval) { + ub_remove_resource_files(uent); + return retval; + } + } + return 0; +} + +int ub_create_sysfs_dev_files(struct ub_entity *uent) +{ + int retval; + + /* config interface */ + retval = sysfs_create_bin_file(&uent->dev.kobj, &ub_config_bin_attr); + if (retval) + goto err; + + retval = ub_create_resource_files(uent); + if (retval) + goto err_config_file; + + retval = ub_create_capabilities_sysfs(uent); + if (retval) + goto err_resource_files; + + return 0; + +err_resource_files: + ub_remove_resource_files(uent); +err_config_file: + sysfs_remove_bin_file(&uent->dev.kobj, &ub_config_bin_attr); +err: + return retval; +} + +void ub_remove_sysfs_ent_files(struct ub_entity *uent) +{ + ub_remove_capabilities_sysfs(uent); + + sysfs_remove_bin_file(&uent->dev.kobj, &ub_config_bin_attr); + + ub_remove_resource_files(uent); +} + +int ub_bus_attr_dynamic_init(void) +{ + int ret; + + ret = bus_create_file(&ub_bus_type, &bus_attr_instance); + if (ret) + return ret; + + ret = bus_create_file(&ub_bus_type, &bus_attr_cluster); + if (ret) + bus_remove_file(&ub_bus_type, &bus_attr_instance); + + return ret; +} + +void ub_bus_attr_dynamic_uninit(void) +{ + bus_remove_file(&ub_bus_type, &bus_attr_cluster); + bus_remove_file(&ub_bus_type, &bus_attr_instance); +} diff --git a/drivers/ub/ubus/sysfs.h b/drivers/ub/ubus/sysfs.h new file mode 100644 index 0000000000000000000000000000000000000000..6716be59183ebb7b2fdcd095fa7a5b10f7610040 --- /dev/null +++ b/drivers/ub/ubus/sysfs.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __SYSFS_H__ +#define __SYSFS_H__ + +struct ub_entity; +extern const struct attribute_group *ub_entity_groups[]; +extern const struct attribute_group *ub_bus_groups[]; +extern const struct device_type ub_dev_type; +int ub_create_sysfs_dev_files(struct ub_entity *pue); +void ub_remove_sysfs_ent_files(struct ub_entity *pue); +int ub_bus_attr_dynamic_init(void); +void ub_bus_attr_dynamic_uninit(void); + +#endif /* __SYSFS_H__ */ diff --git a/drivers/ub/ubus/ub-driver.c b/drivers/ub/ubus/ub-driver.c new file mode 100644 index 0000000000000000000000000000000000000000..c0ebe83984fb6716c66c2b91fdf12f5b4aa5d894 --- /dev/null +++ b/drivers/ub/ubus/ub-driver.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include +#include +#include + +#include "ubus_inner.h" + +static struct fwnode_handle *iommu_fwnode; +void ub_iommu_fwnode_handle_set(const struct fwnode_handle *fwnode) +{ + iommu_fwnode = (struct fwnode_handle *)fwnode; +} +EXPORT_SYMBOL_GPL(ub_iommu_fwnode_handle_set); + +const struct fwnode_handle *ub_iommu_fwnode_handle_get(void) +{ + return iommu_fwnode; +} +EXPORT_SYMBOL_GPL(ub_iommu_fwnode_handle_get); + +static void ubct_dma_setup(struct device *dev) +{ + struct ub_entity *uent = to_ub_entity(dev); + u64 end, mask, size; + u8 addr_limit; + + addr_limit = uent->ubc->attr.mem_size_limit; + if (!addr_limit) { + pr_warn(FW_BUG "ub bus controller missing memory address limit\n"); + return; + } + + size = (addr_limit >= SZ_64) ? U64_MAX : (1ULL << addr_limit); + end = size - 1; + mask = DMA_BIT_MASK(ilog2(end) + 1); + dev->bus_dma_limit = end; + dev->coherent_dma_mask = min(dev->coherent_dma_mask, mask); + *dev->dma_mask = min(*dev->dma_mask, mask); +} + +static int ubct_iommu_fwspec_init(struct device *dev, u32 id, + struct fwnode_handle *fwnode, + bool *fwspec_exists) +{ + int ret; + + *fwspec_exists = dev_iommu_fwspec_get(dev) != NULL; + ret = iommu_fwspec_init(dev, fwnode); + if (ret) + return ret; + if (*fwspec_exists) + return 0; + + return iommu_fwspec_add_ids(dev, &id, 1); +} + +static int ubct_iommu_configure(struct device *dev, bool *fwspec_exists) +{ + if (!iommu_fwnode) { + dev_err(dev, "ubus's iommu_fwnode not ready\n"); + return -ENODEV; + } + + /* input id is 0, ummu not care */ + return ubct_iommu_fwspec_init(dev, 0, iommu_fwnode, fwspec_exists); +} + +static int handle_iommu_configure_error(struct device *dev, int err) +{ + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_warn(dev, "Adding to IOMMU failed: %d\n", err); + return 0; +} + +static int ub_hybrid_iommu_configure(struct device *dev) +{ + bool fwspec_exists; + int err; + + err = ubct_iommu_configure(dev, &fwspec_exists); + if (err) { + if (err != -EPROBE_DEFER) + dev_err(dev, "viot iommu configure: %d\n", err); + + return handle_iommu_configure_error(dev, err); + } + if (fwspec_exists) + return 0; + + if (dev->bus && !device_iommu_mapped(dev)) { + err = iommu_probe_device(dev); + if (err) + return handle_iommu_configure_error(dev, err); + } + + return 0; +} + +static int ub_hybrid_dma_configure(struct device *dev, enum dev_dma_attr attr) +{ + int ret; + + if (attr == DEV_DMA_NOT_SUPPORTED) { + set_dma_ops(dev, &dma_dummy_ops); + return 0; + } + + ubct_dma_setup(dev); + + ret = ub_hybrid_iommu_configure(dev); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + + arch_setup_dma_ops(dev, attr == DEV_DMA_COHERENT); + + return 0; +} + +/* Translate from FW */ +static enum dev_dma_attr ub_dma_attr_trans(u16 dma_attr) +{ + switch (dma_attr) { + case 0: + return DEV_DMA_NON_COHERENT; + case 1: + return DEV_DMA_COHERENT; + default: + return DEV_DMA_NOT_SUPPORTED; + } +} + +static int ub_dma_configure(struct device *dev) +{ + struct ub_driver *udrv = to_ub_driver(dev->driver); + struct ub_entity *uent = to_ub_entity(dev); + struct ub_bus_controller *ubc; + int ret; + + ubc = ub_ubc_get(uent->ubc); + + ret = ub_hybrid_dma_configure(dev, + ub_dma_attr_trans(ubc->attr.dma_cca)); + if (!ret && !udrv->driver_managed_dma) { + ret = iommu_device_use_default_domain(dev); + if (ret) + arch_teardown_dma_ops(dev); + } + + ub_ubc_put(ubc); + dev_info(dev, "dma_configure ret = %d\n", ret); + return ret; +} + +static void ub_dma_cleanup(struct device *dev) +{ + struct ub_driver *driver = to_ub_driver(dev->driver); + + if (!driver->driver_managed_dma) + iommu_device_unuse_default_domain(dev); + + dev_dbg(dev, "dma_cleanup\n"); +} + +struct bus_type ub_bus_type = { + .name = "ub", + .dma_configure = ub_dma_configure, + .dma_cleanup = ub_dma_cleanup, +}; +EXPORT_SYMBOL_GPL(ub_bus_type); + +struct ub_dynid { + struct list_head node; + struct ub_device_id id; +}; + +static void ub_free_dynids(struct ub_driver *drv) +{ + struct ub_dynid *dynid, *n; + + spin_lock(&drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&drv->dynids.lock); +} + +int __ub_register_driver(struct ub_driver *drv, struct module *owner, + const char *mod_name) +{ + if (!drv) + return -EINVAL; + + drv->driver.name = drv->name; + drv->driver.bus = &ub_bus_type; + drv->driver.owner = owner; + drv->driver.mod_name = mod_name; + drv->driver.groups = drv->groups; + + spin_lock_init(&drv->dynids.lock); + INIT_LIST_HEAD(&drv->dynids.list); + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__ub_register_driver); + +void ub_unregister_driver(struct ub_driver *drv) +{ + if (!drv) + return; + + driver_unregister(&drv->driver); + ub_free_dynids(drv); +} +EXPORT_SYMBOL_GPL(ub_unregister_driver); + +static int __init ub_driver_init(void) +{ + return bus_register(&ub_bus_type); +} +postcore_initcall(ub_driver_init); diff --git a/drivers/ub/ubus/ub_black_box.c b/drivers/ub/ubus/ub_black_box.c new file mode 100644 index 0000000000000000000000000000000000000000..ee064165d9928b9812cf547882cd23422ddccb5a --- /dev/null +++ b/drivers/ub/ubus/ub_black_box.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2026. All rights reserved. + */ + +#include + +#include "ub_black_box.h" + +ub_fault_record ub_fault_record_func; +static DEFINE_MUTEX(fault_record_mutex); + +void ub_fault_log(struct ub_entity *uent, u32 event_id, void *data) +{ + int ret; + + /* The data can be a null pointer, the above interface may pass NULL */ + if (!uent) { + pr_err("uent is null\n"); + return; + } + + mutex_lock(&fault_record_mutex); + if (!ub_fault_record_func) { + ub_err(uent, "ub_fault_record_func is not registered\n"); + mutex_unlock(&fault_record_mutex); + return; + } + + ret = ub_fault_record_func(uent->ubc->ctl_no, uent_device(uent), + uent->entity_idx, event_id, data); + if (ret) + ub_err(uent, "ub_fault_record_func execute error, ret=%d\n", + ret); + + mutex_unlock(&fault_record_mutex); +} +EXPORT_SYMBOL_GPL(ub_fault_log); + +int ub_fault_register(ub_fault_record record) +{ + if (!record) + return -EINVAL; + + mutex_lock(&fault_record_mutex); + ub_fault_record_func = record; + mutex_unlock(&fault_record_mutex); + pr_info("ub ub_fault_record register successfully\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(ub_fault_register); + +void ub_fault_unregister(void) +{ + mutex_lock(&fault_record_mutex); + ub_fault_record_func = NULL; + mutex_unlock(&fault_record_mutex); +} +EXPORT_SYMBOL_GPL(ub_fault_unregister); + +int ub_fault_vdm(u32 ctl_no, struct ub_vdm_pld *vdm_pld) +{ + struct ub_bus_controller *ubc = NULL; + struct ub_bus_controller *tmp; + int ret; + + if (!vdm_pld) { + pr_err("vdm_pld is null\n"); + return -EINVAL; + } + + list_for_each_entry(tmp, &ubc_list, node) { + if (tmp->ctl_no == ctl_no) { + ubc = tmp; + break; + } + } + + if (!ubc) { + pr_err("ctl_no %u not found in ubc_list\n", ctl_no); + return -ENODEV; + } + + ret = ub_vdm_message(ubc->uent, vdm_pld); + if (ret) + pr_err("ub_vdm_message execute error, ret=%d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(ub_fault_vdm); diff --git a/drivers/ub/ubus/ub_black_box.h b/drivers/ub/ubus/ub_black_box.h new file mode 100644 index 0000000000000000000000000000000000000000..3e515157ac1a0971f2b0f6014a518263888bd6ba --- /dev/null +++ b/drivers/ub/ubus/ub_black_box.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2026. All rights reserved. + */ + +#ifndef __UB_BLACK_BOX_H__ +#define __UB_BLACK_BOX_H__ + +#include "ubus.h" + +typedef int (*ub_fault_record)(u32 ctl_no, u32 device_id, u32 entity_idx, + u32 event_id, void *data); + +void ub_fault_log(struct ub_entity *uent, u32 event_id, void *data); +int ub_fault_register(ub_fault_record record); +void ub_fault_unregister(void); +int ub_fault_vdm(u32 ctl_no, struct ub_vdm_pld *vdm_pld); + +#endif /* __UB_BLACK_BOX_H__ */ diff --git a/drivers/ub/ubus/ubus.h b/drivers/ub/ubus/ubus.h new file mode 100644 index 0000000000000000000000000000000000000000..229b027a8ef7e19e91eabbe3d2c1a88152c8e86a --- /dev/null +++ b/drivers/ub/ubus/ubus.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __UBUS_H__ +#define __UBUS_H__ + +#include +#include + +#define UB_CP_UPI 0x7FFF + +enum ub_entity_type { + UB_ENT_BUS_CONTROLLER, + UB_ENT_IBUS_CONTROLLER, + UB_ENT_SWITCH, + UB_ENT_DEVICE, + UB_ENT_IDEVICE, + UB_ENT_P_DEVICE, + UB_ENT_P_IDEVICE, + UB_ENT_UNKNOWN +}; + +#define is_primary(uent) ((uent)->entity_idx == 0) +#define is_ibus_controller(uent) ((uent)->ent_type == UB_ENT_IBUS_CONTROLLER) +#define is_bus_controller(uent) ((uent)->ent_type == UB_ENT_BUS_CONTROLLER) +#define is_controller(uent) (is_ibus_controller(uent) || is_bus_controller(uent)) +#define is_switch(uent) ((uent)->ent_type == UB_ENT_SWITCH) +#define is_device(uent) ((uent)->ent_type == UB_ENT_DEVICE) +#define is_p_device(uent) ((uent)->ent_type == UB_ENT_P_DEVICE) +#define is_dev(uent) (is_device(uent) || is_p_device(uent)) +#define is_idevice(uent) ((uent)->ent_type == UB_ENT_IDEVICE) +#define is_p_idevice(uent) ((uent)->ent_type == UB_ENT_P_IDEVICE) +#define is_idev(uent) (is_idevice(uent) || is_p_idevice(uent)) + +#define ONE_CNA(uent) (is_switch(uent) || (uent)->port_nums == 1) + +#define UB_COMPACT_EID_MASK GENMASK(19, 0) +#define UB_UPI_MASK GENMASK(14, 0) + +/* ub_entity priv_flags */ +#define UB_ENTITY_START 0 /* Flag indicate uent can match with driver */ +#define UB_ENTITY_DETACHED 1 /* Flag indicate uent is detached */ +#define UB_ENTITY_ROUTE_UPDATED 2 /* Flag indicate uent's route is updated */ +#define UB_ENTITY_SETUP 4 /* Flag indicate uent is setup */ +#define UB_ENTITY_ACTIVE 5 /* Flag indicate uent is in normal or disable state */ +#define UB_ENTITY_DISCONNECTED 6 /* Flag indicate uent is disconnected */ + +static inline void ub_entity_assign_priv_flag(struct ub_entity *uent, int bit, + bool flag) +{ + assign_bit(bit, &uent->priv_flags, flag); +} + +static inline bool ub_entity_test_priv_flag(struct ub_entity *uent, int bit) +{ + return test_bit(bit, &uent->priv_flags); +} + +struct ub_bus_controller *ub_find_bus_controller(u32 ctl_no); + +struct ub_manage_subsystem_ops { + u32 vendor; + int (*controller_probe)(struct ub_bus_controller *ubc); + void (*controller_remove)(struct ub_bus_controller *ubc); + int (*ras_handler_probe)(void); + void (*ras_handler_remove)(void); + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) +}; +int register_ub_manage_subsystem_ops(const struct ub_manage_subsystem_ops *ops); +void unregister_ub_manage_subsystem_ops(const struct ub_manage_subsystem_ops *ops); +const struct ub_manage_subsystem_ops *get_ub_manage_subsystem_ops(void); + +#endif /* __UBUS_H__ */ diff --git a/drivers/ub/ubus/ubus_config.c b/drivers/ub/ubus/ubus_config.c new file mode 100644 index 0000000000000000000000000000000000000000..a0dea1c399018484d76cf31ee2518b4ffe684f82 --- /dev/null +++ b/drivers/ub/ubus/ubus_config.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus config: " fmt + +#include "ubus.h" +#include "msg.h" +#include "ubus_config.h" +#include "ubus_inner.h" + +struct cfg_msg_pld_req { + /* DW0 */ + u32 rsvd0 : 4; + u32 byte_enable : 4; + u32 rsvd1 : 8; + u32 entity_idx : 16; + /* DW1 */ + u32 req_addr; + /* DW2 */ + u32 rsvd2; + /* DW3 */ + u32 write_data; +}; + +struct cfg_msg_pld_rsp { + /* DW0 */ + u32 read_data; + /* DW1 */ + u32 rsvd1; + /* DW2 */ + u32 rsvd2; + /* DW3 */ + u32 rsvd3; +}; + +struct cfg_msg_req_pkt { + struct msg_pkt_header header; + struct cfg_msg_pld_req req_payload; +}; + +struct cfg_msg_rsp_pkt { + struct msg_pkt_header header; + struct cfg_msg_pld_rsp rsp_payload; +}; + +enum ub_cfg_sub_msg_code { + UB_CFG0_READ = 0, + UB_CFG0_WRITE = 1, + UB_CFG1_READ = 2, + UB_CFG1_WRITE = 3 +}; + +#define CFG_MSG_PLD_SIZE 16 + +struct ub_cfg { + u8 size; + u8 pos_mask; + u8 byte_mask; +}; + +static struct ub_cfg ub_cfg_param[] = { { 0x1, 0x0, 0x1 }, + { 0x2, 0x1, 0x3 }, + { 0x4, 0x3, 0xf } }; +#define UB_CFG_PARAM_CNT ARRAY_SIZE(ub_cfg_param) + +static bool pos_size_valid(u64 pos, u8 size) +{ + struct ub_cfg *cfg; + u8 i; + + if (pos >= UB_CFG_SAPCE_SLICE_END) + return false; + + for (i = 0; i < UB_CFG_PARAM_CNT; i++) { + cfg = &ub_cfg_param[i]; + if (cfg->size == size) + return !(pos & cfg->pos_mask); + } + return false; /* size invalid */ +} + +static void ub_msg_extended_header_init(struct msg_extended_header *msgetah, + u16 plen, u8 code) +{ + msgetah->plen = plen; + msgetah->msg_code = msg_code(code); + msgetah->type = msg_type(code); + msgetah->sub_msg_code = sub_msg_code(code); +} + +void ub_msg_pkt_header_init(struct msg_pkt_header *header, struct ub_entity *uent, + u16 plen, u8 code, bool flag) +{ + struct compact_network_header *cnth = &header->nth; + struct ub_link_header *ulh = &header->ulh; + struct ub_entity *ubc_uent = uent->ubc->uent; + u32 seid = ubc_uent->eid; + u16 scna = (u16)ubc_uent->cna; + u32 deid = uent->eid; + u16 dcna = (u16)uent->cna; + + if (flag) { + dcna = scna; + deid = seid; + } + + ulh->cfg = UB_COMPACT_LINK_CFG; + + cnth->nth_nlp = NTH_NLP_WITH_TPH; + cnth->scna = scna; + cnth->dcna = dcna; + cnth->mgmt = (is_p_device(uent) || + (uent->pool && uent_type(uent) == UB_TYPE_CONTROLLER)) + ? 0 : 1; + + header->ctph_nlp = CTPH_NLP_UPI_40BITS_UEID; + header->tp_opcode = CTPH_OPCODE_NOT_CNP; + header->pad = 0; + header->upi = uent->ubc->cluster ? uent->upi : UB_CP_UPI; + header->seid_h = seid_high(seid); + header->seid_l = seid_low(seid); + header->deid = deid; + header->ta_opcode = TAH_OPCODE_MSG; + + ub_msg_extended_header_init(&header->msgetah, plen, code); +} +EXPORT_SYMBOL_GPL(ub_msg_pkt_header_init); + +int ub_check_cfg_msg_code(struct device *dev, u8 req_code, u8 rsp_code) +{ + u8 req_sub = sub_msg_code(req_code); + u8 rsp_sub = sub_msg_code(rsp_code); + u8 req = msg_code(req_code); + u8 rsp = msg_code(rsp_code); + + if (req != UB_MSG_CODE_CFG || rsp != UB_MSG_CODE_CFG) { + dev_err(dev, "The message code is incorrect, req message code=%#x, rsp message code=%#x\n", + req, rsp); + return -EIO; + } + + if (msg_type(req_code) == MSG_REQ && + msg_type(rsp_code) == MSG_RSP && req_sub == rsp_sub) + return 0; + + dev_err(dev, "The response code is incorrect, req code=%#x, rsp code=%#x\n", + req_code, rsp_code); + return -EIO; +} +EXPORT_SYMBOL_GPL(ub_check_cfg_msg_code); + +static int ub_sync_cfg_rsp_check(struct ub_entity *uent, + struct cfg_msg_req_pkt *req_pkt, + struct cfg_msg_rsp_pkt *rsp_pkt) +{ + struct msg_pkt_header *req_header = &req_pkt->header; + struct msg_pkt_header *rsp_header = &rsp_pkt->header; + u32 rsp_seid = eid_gen(rsp_header->seid_h, rsp_header->seid_l); + u32 req_seid = eid_gen(req_header->seid_h, req_header->seid_l); + u8 rsp_status = rsp_header->msgetah.rsp_status; + + if (rsp_status != UB_MSG_RSP_SUCCESS) { + ub_err(uent, "Message response error, rsp_status=%#x\n", + rsp_status); + return -EIO; + } + + if (rsp_header->nth.scna != req_header->nth.dcna || + rsp_header->nth.dcna != req_header->nth.scna) { + ub_err(uent, "CNA mismatch, req_scna=%#x req_dcna=%#x, rsp_scna=%#x rsp_dcna=%#x\n", + req_header->nth.scna, req_header->nth.dcna, + rsp_header->nth.scna, rsp_header->nth.dcna); + return -EIO; + } + + if (rsp_seid != req_header->deid || rsp_header->deid != req_seid) { + ub_err(uent, "EID mismatch, req_seid=%#x req_deid=%#x, rsp_seid=%#x rsp_deid=%#x\n", + req_seid, req_header->deid, rsp_seid, rsp_header->deid); + return -EIO; + } + + if (rsp_header->msgetah.plen != CFG_MSG_PLD_SIZE) { + ub_err(uent, "The packet length is incorrect, len=%#x\n", + rsp_header->msgetah.plen); + return -EIO; + } + + return ub_check_cfg_msg_code(&uent->dev, req_header->msgetah.code, + rsp_header->msgetah.code); +} + +static void ub_sync_cfg_rsp_handle(struct cfg_msg_pld_rsp *rsp, u8 size, + u64 pos, bool write, u32 *val) +{ +#define UB_CFG_REG_SIZE 4 + u8 pos_in_reg = pos % UB_CFG_REG_SIZE; + u32 read_data; + + if (!write) { + read_data = rsp->read_data >> (pos_in_reg * BITS_PER_BYTE); + if (size == sizeof(u8)) + *(u8 *)val = read_data; + else if (size == sizeof(u16)) + *(u16 *)val = read_data; + else + *val = read_data; + } +} + +static u8 gen_cfg_sub_msg_code(bool is_write, u64 pos) +{ + if (pos >= UB_CFG1_BASIC_SLICE && pos < UB_PORT_SLICE_START) + return is_write ? UB_CFG1_WRITE : UB_CFG1_READ; + else + return is_write ? UB_CFG0_WRITE : UB_CFG0_READ; +} + +static void ub_msg_pkt_req_init(struct ub_entity *uent, u8 size, u64 pos, u32 *val, + struct cfg_msg_pld_req *req) +{ + u8 bt_mask = ub_cfg_param[size >> 1].byte_mask; + + if (val) { + if (size == sizeof(u8)) + req->write_data = *(u8 *)val; + else if (size == sizeof(u16)) + req->write_data = *(u16 *)val; + else + req->write_data = *val; + req->write_data <<= ((pos % sizeof(u32)) * BITS_PER_BYTE); + } + + req->byte_enable = bt_mask << (u8)(pos % sizeof(u32)); + req->entity_idx = uent->entity_idx; + /* The address is in four bytes. */ + req->req_addr = pos / sizeof(u32); +} + +static bool ub_entity_is_disconnected(struct ub_entity *uent) +{ + struct ub_entity *pri = uent; + + if (uent->pool && uent_type(uent) == UB_TYPE_CONTROLLER) + return false; /* Pool entity not consider here. */ + + while (pri->entity_idx != 0) + pri = pri->pue; + + return ub_entity_test_priv_flag(pri, UB_ENTITY_DISCONNECTED); +} + +static int ub_sync_cfg(struct ub_entity *uent, u8 size, u64 pos, bool iswrite, + u32 *val) +{ + struct cfg_msg_req_pkt req_pkt = {}; + struct cfg_msg_rsp_pkt rsp_pkt = {}; + struct msg_info info = {}; + u8 sub_msg_code; + int ret; + + if (ub_entity_is_disconnected(uent)) + return -ETIMEDOUT; + + if (!pos_size_valid(pos, size)) { + ub_err(uent, "pos or size invalid, pos=%#llx, size=%#x\n", pos, + size); + return -EINVAL; + } + + if (!iswrite) + memset(val, 0xFF, size); + + sub_msg_code = gen_cfg_sub_msg_code(iswrite, pos); + ub_msg_pkt_header_init(&req_pkt.header, uent, CFG_MSG_PLD_SIZE, + code_gen(UB_MSG_CODE_CFG, sub_msg_code, + MSG_REQ), false); + + ub_msg_pkt_req_init(uent, size, pos, (iswrite ? val : NULL), + &req_pkt.req_payload); + + message_info_init(&info, uent, &req_pkt, &rsp_pkt, + (MSG_CFG_PKT_SIZE << MSG_REQ_SIZE_OFFSET) | + MSG_CFG_PKT_SIZE); + ret = message_sync_request(uent->message->mdev, &info, + req_pkt.header.msgetah.code); + if (ret) + return ret; + + ret = ub_sync_cfg_rsp_check(uent, &req_pkt, &rsp_pkt); + if (!ret) + ub_sync_cfg_rsp_handle(&rsp_pkt.rsp_payload, size, pos, iswrite, val); + + return ret; +} + +int ub_send_cfg(struct ub_entity *uent, u8 size, u64 pos, u32 *val) +{ + struct cfg_msg_req_pkt req_pkt = {}; + struct msg_info info = {}; + u8 sub_msg_code; + + if (!uent || !uent->message || !uent->message->mdev) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + if (!pos_size_valid(pos, size)) { + pr_err("pos or size invalid, pos=%#llx, size=%u\n", pos, size); + return -EINVAL; + } + + sub_msg_code = gen_cfg_sub_msg_code(true, pos); + ub_msg_pkt_header_init(&req_pkt.header, uent, CFG_MSG_PLD_SIZE, + code_gen(UB_MSG_CODE_CFG, sub_msg_code, + MSG_REQ), false); + + ub_msg_pkt_req_init(uent, size, pos, val, + &req_pkt.req_payload); + + message_info_init(&info, uent, &req_pkt, NULL, + (MSG_CFG_PKT_SIZE << MSG_REQ_SIZE_OFFSET) | + MSG_CFG_PKT_SIZE); + return message_send(uent->message->mdev, &info, + req_pkt.header.msgetah.code); +} + +static int __ub_cfg_read_byte(struct ub_entity *uent, u64 pos, u8 *val) +{ + if (!uent || !uent->message || !uent->message->mdev || !val) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + return ub_sync_cfg(uent, (u8)sizeof(u8), pos, false, (u32 *)val); +} + +static int __ub_cfg_read_word(struct ub_entity *uent, u64 pos, u16 *val) +{ + if (!uent || !uent->message || !uent->message->mdev || !val) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + return ub_sync_cfg(uent, (u8)sizeof(u16), pos, false, (u32 *)val); +} + +static int __ub_cfg_read_dword(struct ub_entity *uent, u64 pos, u32 *val) +{ + if (!uent || !uent->message || !uent->message->mdev || !val) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + return ub_sync_cfg(uent, (u8)sizeof(u32), pos, false, val); +} + +static int __ub_cfg_write_byte(struct ub_entity *uent, u64 pos, u8 val) +{ + if (!uent || !uent->message || !uent->message->mdev) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + return ub_sync_cfg(uent, (u8)sizeof(u8), pos, true, (u32 *)&val); +} + +static int __ub_cfg_write_word(struct ub_entity *uent, u64 pos, u16 val) +{ + if (!uent || !uent->message || !uent->message->mdev) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + return ub_sync_cfg(uent, (u8)sizeof(u16), pos, true, (u32 *)&val); +} + +static int __ub_cfg_write_dword(struct ub_entity *uent, u64 pos, u32 val) +{ + if (!uent || !uent->message || !uent->message->mdev) { + pr_err("uent or message or mdev is null\n"); + return -EINVAL; + } + + return ub_sync_cfg(uent, (u8)sizeof(u32), pos, true, (u32 *)&val); +} + +int ub_cfg_ops_init(void) +{ + int ret; + + ret = register_ub_cfg_read_ops(__ub_cfg_read_byte, __ub_cfg_read_word, + __ub_cfg_read_dword); + if (ret) + return ret; + + ret = register_ub_cfg_write_ops(__ub_cfg_write_byte, + __ub_cfg_write_word, + __ub_cfg_write_dword); + if (ret) + unregister_ub_cfg_ops(); + + return ret; +} diff --git a/drivers/ub/ubus/ubus_config.h b/drivers/ub/ubus/ubus_config.h new file mode 100644 index 0000000000000000000000000000000000000000..5971c87e481b32825beb47e37437104b9f18dc33 --- /dev/null +++ b/drivers/ub/ubus/ubus_config.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UBUS_CONFIG_H__ +#define __UBUS_CONFIG_H__ + +struct ub_entity; +int ub_cfg_ops_init(void); +int ub_check_cfg_msg_code(struct device *dev, u8 req_code, u8 rsp_code); +int ub_send_cfg(struct ub_entity *uent, u8 size, u64 pos, u32 *val); + +#endif /* __UBUS_CONFIG_H__ */ diff --git a/drivers/ub/ubus/ubus_controller.c b/drivers/ub/ubus/ubus_controller.c new file mode 100644 index 0000000000000000000000000000000000000000..007951070766ea7d7a020ce6d2026073bb9a0fb5 --- /dev/null +++ b/drivers/ub/ubus/ubus_controller.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus controller: " fmt + +#include + +#include "ubus.h" +#include "ubus_controller.h" + +int ub_ubc_to_node(struct ub_bus_controller *ubc) +{ + return dev_to_node(&ubc->dev); +} + +void ub_bus_controllers_remove(void) +{ + const struct ub_manage_subsystem_ops *manage_subsystem_ops; + struct ub_bus_controller *ubc; + + manage_subsystem_ops = get_ub_manage_subsystem_ops(); + if (!manage_subsystem_ops || !manage_subsystem_ops->controller_remove) + return; + + list_for_each_entry_reverse(ubc, &ubc_list, node) + if (ubc->ops) + manage_subsystem_ops->controller_remove(ubc); +} + +int ub_bus_controllers_probe(void) +{ + const struct ub_manage_subsystem_ops *manage_subsystem_ops; + struct ub_bus_controller *ubc; + int ret; + + manage_subsystem_ops = get_ub_manage_subsystem_ops(); + if (!manage_subsystem_ops || !manage_subsystem_ops->controller_probe) + return -EINVAL; + + list_for_each_entry(ubc, &ubc_list, node) { + ret = manage_subsystem_ops->controller_probe(ubc); + if (ret) + goto out; + } + + return 0; +out: + ub_bus_controllers_remove(); + return ret; +} + +struct ub_bus_controller *ub_find_bus_controller_by_cna(u32 cna) +{ + struct ub_bus_controller *ubc; + + if (!get_ub_manage_subsystem_ops()) { + pr_err("manage subsystem ops is null\n"); + return NULL; + } + + list_for_each_entry(ubc, &ubc_list, node) + if (ubc->uent->cna == cna) + return ubc; + + return NULL; +} diff --git a/drivers/ub/ubus/ubus_controller.h b/drivers/ub/ubus/ubus_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..9ead5a1bae5ac4887305bbddbf8a324a41b891b4 --- /dev/null +++ b/drivers/ub/ubus/ubus_controller.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UBUS_CONTROLLER_H__ +#define __UBUS_CONTROLLER_H__ + +#include +#include "decoder.h" + +struct ub_bus_controller; +struct ub_bus_controller_ops { + int (*eu_table_init)(struct ub_bus_controller *ubc); + void (*eu_table_uninit)(struct ub_bus_controller *ubc); + int (*eu_cfg)(struct ub_bus_controller *ubc, bool flag, u32 eid, u16 upi); + int (*mem_decoder_create)(struct ub_bus_controller *ubc); + void (*mem_decoder_remove)(struct ub_bus_controller *ubc); + void (*register_ubmem_irq)(struct ub_bus_controller *ubc); + void (*unregister_ubmem_irq)(struct ub_bus_controller *ubc); + int (*init_decoder_queue)(struct ub_decoder *decoder); + void (*uninit_decoder_queue)(struct ub_decoder *decoder); + int (*entity_enable)(struct ub_entity *uent, u8 enable); + int (*create_decoder_table)(struct ub_decoder *decoder); + void (*free_decoder_table)(struct ub_decoder *decoder); + int (*decoder_map)(struct ub_decoder *decoder, + struct decoder_map_info *info); + int (*decoder_unmap)(struct ub_decoder *decoder, phys_addr_t addr, + u64 size); + void (*decoder_event_deal)(struct ub_decoder *decoder); + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) + CK_KABI_RESERVE(5) + CK_KABI_RESERVE(6) + CK_KABI_RESERVE(7) + CK_KABI_RESERVE(8) +}; + +struct ub_bus_controller *ub_find_bus_controller_by_cna(u32 cna); +void ub_bus_controllers_remove(void); +int ub_bus_controllers_probe(void); +int ub_ubc_to_node(struct ub_bus_controller *ubc); + +#endif /* __UBUS_CONTROLLER_H__ */ diff --git a/drivers/ub/ubus/ubus_driver.c b/drivers/ub/ubus/ubus_driver.c new file mode 100644 index 0000000000000000000000000000000000000000..7f7e7ac8d4485ea1fe19055ab2229895a848de43 --- /dev/null +++ b/drivers/ub/ubus/ubus_driver.c @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002-2004, 2007 Greg Kroah-Hartman + * (C) Copyright 2007 Novell Inc. + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + * + * Thanks to Greg Kroah-Hartman and Novell Inc. for their previous work. + * + */ + +#define pr_fmt(fmt) "ubus driver: " fmt + +#include +#include + +#include "services.h" +#include "msg.h" +#include "enum.h" +#include "instance.h" +#include "ioctl.h" +#include "sysfs.h" +#include "ubus.h" +#include "ubus_config.h" +#include "ubus_controller.h" +#include "ubus_inner.h" +#include "ubus_entity.h" +#include "services/service.h" +#include "ubus_driver.h" + +bool entity_flex_en; +module_param(entity_flex_en, bool, 0444); +MODULE_PARM_DESC(entity_flex_en, "Entity Flexible enable: default: 0"); + +bool msg_retry; +EXPORT_SYMBOL_GPL(msg_retry); +module_param(msg_retry, bool, 0444); +MODULE_PARM_DESC(msg_retry, "support msg retry: 0(disable)"); + +DECLARE_RWSEM(ub_bus_sem); + +#define UBC_GUID_VENDOR_SHIFT 48 +#define UBC_GUID_VENDOR_MASK GENMASK(15, 0) + +static DEFINE_MUTEX(manage_subsystem_ops_mutex); +static const struct ub_manage_subsystem_ops *manage_subsystem_ops; + +const struct ub_manage_subsystem_ops *get_ub_manage_subsystem_ops(void) +{ + return manage_subsystem_ops; +} + +struct ub_bus_controller *ub_find_bus_controller(u32 ctl_no) +{ + struct ub_bus_controller *ubc; + + list_for_each_entry(ubc, &ubc_list, node) + if (ubc->ctl_no == ctl_no) + return ubc; + + return NULL; +} +EXPORT_SYMBOL_GPL(ub_find_bus_controller); + +int ub_get_bus_controller(struct ub_entity *ubc_dev[], unsigned int max_num, + unsigned int *real_num) +{ + struct ub_bus_controller *ubc; + unsigned int ubc_num = 0; + int ret; + + if (!manage_subsystem_ops) { + pr_err("manage subsystem ops is null\n"); + return -EINVAL; + } + + if (!real_num || !ubc_dev) { + pr_err("%s: input parameters invalid\n", __func__); + return -EINVAL; + } + + list_for_each_entry(ubc, &ubc_list, node) { + if (ubc_num >= max_num) { + pr_err("ubc list num over max num %u\n", max_num); + ret = -ENOMEM; + goto ubc_put; + } + + if (!ub_entity_get(ubc->uent)) { + pr_err("The ub_entity of ubc is null\n"); + ret = -EINVAL; + goto ubc_put; + } + ubc_dev[ubc_num] = ubc->uent; + ubc_num++; + } + *real_num = ubc_num; + + return 0; + +ubc_put: + ub_put_bus_controller(ubc_dev, max_num); + return ret; +} +EXPORT_SYMBOL_GPL(ub_get_bus_controller); + +void ub_put_bus_controller(struct ub_entity *ubc_dev[], unsigned int num) +{ + unsigned int i; + + if (ubc_dev) { + for (i = 0; i < num; i++) + ub_entity_put(ubc_dev[i]); + memset(ubc_dev, 0, sizeof(struct ub_entity *) * num); + } +} +EXPORT_SYMBOL_GPL(ub_put_bus_controller); + +struct ub_entity *ub_entity_get(struct ub_entity *dev) +{ + if (dev) + get_device(&dev->dev); + return dev; +} +EXPORT_SYMBOL_GPL(ub_entity_get); + +void ub_entity_put(struct ub_entity *dev) +{ + if (dev) + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(ub_entity_put); + +struct ub_dynid { + struct list_head node; + struct ub_device_id id; +}; + +static const struct ub_device_id ub_entity_id_any = { + .vendor = (__u32)UB_ANY_ID, + .device = (__u32)UB_ANY_ID, + .mod_vendor = (__u32)UB_ANY_ID, + .module = (__u32)UB_ANY_ID, +}; + +static inline const struct ub_device_id * +ub_match_one_device(const struct ub_device_id *id, const struct ub_entity *dev) +{ + if ((id->vendor == UB_ANY_ID || id->vendor == uent_vendor(dev)) && + (id->device == UB_ANY_ID || id->device == uent_device(dev)) && + (id->mod_vendor == UB_ANY_ID || id->mod_vendor == dev->mod_vendor) && + (id->module == UB_ANY_ID || id->module == dev->module) && + !((id->class_code ^ uent_class(dev)) & id->class_mask)) + return id; + return NULL; +} + +static const struct ub_device_id *ub_match_id(const struct ub_device_id *ids, + struct ub_entity *dev) +{ + if (ids && dev) { + while (ids->vendor || ids->mod_vendor || ids->class_mask) { + if (ub_match_one_device(ids, dev)) + return ids; + ids++; + } + } + return NULL; +} + +static struct ub_entity * +ub_get_dev_by_id_inner(struct ub_entity *from, const void *data, + int (*match)(struct device *dev, const void *data)) +{ + struct device *dev, *dev_start = NULL; + struct ub_entity *fdev = NULL; + + if (from) + dev_start = &from->dev; + + dev = bus_find_device(&ub_bus_type, dev_start, (void *)data, match); + if (dev) + fdev = to_ub_entity(dev); + + ub_entity_put(from); + return fdev; +} + +static int ub_entity_match_by_guid(struct device *dev, const void *data) +{ + const struct ub_guid *guid = (const struct ub_guid *)data; + struct ub_entity *uent = to_ub_entity(dev); + + if (guid_equal(&uent->guid.id, &guid->id)) + return 1; + return 0; +} + +struct ub_entity *ub_get_ent_by_guid(const struct ub_guid *guid) +{ + return ub_get_dev_by_id_inner(NULL, guid, ub_entity_match_by_guid); +} +EXPORT_SYMBOL_GPL(ub_get_ent_by_guid); + +static int ub_entity_match_by_eid(struct device *dev, const void *data) +{ + struct ub_entity *uent = to_ub_entity(dev); + const u32 eid = *((const u32 *)data); + + if (uent->eid == eid) + return 1; + + return 0; +} + +struct ub_entity *ub_get_ent_by_eid(unsigned int eid) +{ + return ub_get_dev_by_id_inner(NULL, &eid, ub_entity_match_by_eid); +} +EXPORT_SYMBOL_GPL(ub_get_ent_by_eid); + +static int ub_entity_match_by_uent_num(struct device *dev, const void *data) +{ + const u32 uent_num = *((const u32 *)data); + struct ub_entity *uent = to_ub_entity(dev); + + if (uent->uent_num == uent_num) + return 1; + + return 0; +} + +struct ub_entity *ub_get_ent_by_uent_num(unsigned int uent_num) +{ + return ub_get_dev_by_id_inner(NULL, &uent_num, ub_entity_match_by_uent_num); +} +EXPORT_SYMBOL_GPL(ub_get_ent_by_uent_num); + +static const struct ub_device_id *ub_match_device(struct ub_driver *drv, + struct ub_entity *dev) +{ + const struct ub_device_id *found_id = NULL, *ids; + struct ub_dynid *dynid; + + /* When driver_override is set, only bind to the matching driver */ + if (dev->driver_override && strcmp(dev->driver_override, drv->name)) + return NULL; + + /* Look at the dynamic ids first, before the static ones */ + spin_lock(&drv->dynids.lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (ub_match_one_device(&dynid->id, dev)) { + found_id = &dynid->id; + break; + } + } + spin_unlock(&drv->dynids.lock); + + if (found_id) + return found_id; + + for (ids = drv->id_table; (found_id = ub_match_id(ids, dev)); + ids = found_id + 1) { + if (found_id->override_only) { + if (dev->driver_override) + return found_id; + } else { + return found_id; + } + } + + /* driver_override will always match, send a dummy id */ + if (dev->driver_override) + return &ub_entity_id_any; + + return NULL; +} + +static int ub_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ub_driver *ub_drv = to_ub_driver(drv); + struct ub_entity *ub_entity = to_ub_entity(dev); + const struct ub_device_id *found_id; + + if (!ub_entity->match_driver) + return 0; + + found_id = ub_match_device(ub_drv, ub_entity); + if (found_id) + return 1; + + return 0; +} + +static int ub_call_probe(struct ub_driver *drv, struct ub_entity *dev, + const struct ub_device_id *id) +{ + int ret; + + dev->driver = drv; + /* + * Probe function should return < 0 for failure, 0 for success + * Treat values > 0 as success, but warn. + */ + ret = drv->probe(dev, id); + if (ret < 0) { + dev->driver = NULL; + return ret; + } else if (ret > 0) { + ub_warn(dev, "Driver probe function unexpectedly, ret=%d\n", + ret); + } + + return 0; +} + +static int __ub_entity_probe(struct ub_driver *drv, struct ub_entity *dev) +{ + const struct ub_device_id *id; + int ret = 0; + + if (drv->probe) { + ret = -ENODEV; + + id = ub_match_device(drv, dev); + if (id) + ret = ub_call_probe(drv, dev, id); + } + + return ret; +} + +static int ub_entity_probe(struct device *dev) +{ + struct ub_driver *drv = to_ub_driver(dev->driver); + struct ub_entity *ub_entity = to_ub_entity(dev); + struct ub_entity *pue; + u16 entity_idx; + int ret; + + ub_entity_get(ub_entity); + if (!ub_entity->is_mue) { + pue = ub_entity->pue; + entity_idx = ub_entity->entity_idx; + ub_virt_notify(pue, entity_idx, true); + } + + ret = __ub_entity_probe(drv, ub_entity); + if (ret) + ub_entity_put(ub_entity); + return ret; +} + +static void ub_entity_remove(struct device *dev) +{ + struct ub_entity *ub_entity = to_ub_entity(dev); + struct ub_driver *drv = ub_entity->driver; + + if (drv->remove) + drv->remove(ub_entity); + + ub_entity->driver = NULL; + + ub_entity_put(ub_entity); +} + +static void ub_entity_shutdown(struct device *dev) +{ + struct ub_entity *uent = to_ub_entity(dev); + struct ub_driver *drv = uent->driver; + + ub_dbg(uent, "come shutdown\n"); + + pm_runtime_resume(dev); + + if (drv && drv->shutdown) + drv->shutdown(uent); +} + +static int ub_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + struct ub_entity *uent; + + if (!dev) + return -ENODEV; + + uent = to_ub_entity(dev); + + if (add_uevent_var(env, "UB_ID=%04X:%04X", uent_vendor(uent), + uent_device(uent))) + return -ENOMEM; + + if (add_uevent_var(env, "UB_MODULE=%04X:%04X", uent->mod_vendor, + uent->module)) + return -ENOMEM; + + if (add_uevent_var(env, "UB_TYPE=%01X", uent_type(uent))) + return -ENOMEM; + + if (add_uevent_var(env, "UB_CLASS=%04X", uent_class(uent))) + return -ENOMEM; + + if (add_uevent_var(env, "UB_VERSION=%01X", uent_version(uent))) + return -ENOMEM; + + if (add_uevent_var(env, "UB_SEQ_NUM=%016llX", uent_seq(uent))) + return -ENOMEM; + + if (add_uevent_var(env, "UB_ENTITY_NAME=%s", ub_name(uent))) + return -ENOMEM; + + if (add_uevent_var(env, "MODALIAS=ub:v%04Xd%04Xmv%04Xm%04Xc%04X", + uent_vendor(uent), uent_device(uent), + uent->mod_vendor, uent->module, uent_class(uent))) + return -ENOMEM; + + return 0; +} + +static int ub_add_dynid(struct ub_driver *drv, + unsigned int vendor, unsigned int device, + unsigned int mod_vendor, unsigned int module, + unsigned int class_code, unsigned int class_mask, + unsigned long driver_data) +{ + struct ub_dynid *dynid; + + dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + if (!dynid) + return -ENOMEM; + + dynid->id.vendor = vendor; + dynid->id.device = device; + dynid->id.mod_vendor = mod_vendor; + dynid->id.module = module; + dynid->id.class_code = class_code; + dynid->id.class_mask = class_mask; + dynid->id.driver_data = driver_data; + + spin_lock(&drv->dynids.lock); + list_add_tail(&dynid->node, &drv->dynids.list); + spin_unlock(&drv->dynids.lock); + + return driver_attach(&drv->driver); +} + +static int ub_check_id_exist(struct ub_driver *udrv, u32 vendor, u32 device, + u32 mod_vendor, u32 module, u32 class_code) +{ + struct ub_entity *uent; + int ret = 0; + + uent = kzalloc(sizeof(struct ub_entity), GFP_KERNEL); + if (!uent) + return -ENOMEM; + + uent_vendor(uent) = vendor; + uent_device(uent) = device; + uent_class(uent) = class_code; + uent->mod_vendor = mod_vendor; + uent->module = module; + + if (ub_match_device(udrv, uent)) + ret = -EEXIST; + + kfree(uent); + + return ret; +} + +static int ub_check_driver_data_valid(const struct ub_device_id *ids, + unsigned long driver_data) +{ + /* Only accept driver_data values that match an exiting id_table entry */ + while (ids->vendor || ids->mod_vendor || ids->class_mask) { + if (driver_data == ids->driver_data) + return 0; + ids++; + } + + return -EINVAL; +} + +static ssize_t new_id_store(struct device_driver *driver, const char *buf, + size_t count) +{ + u32 vendor, device, class_code = 0, class_mask = 0; + u32 mod_vendor = UB_ANY_ID, module = UB_ANY_ID; + struct ub_driver *udrv = to_ub_driver(driver); + unsigned long driver_data = 0; + int fileds, ret; + + fileds = sscanf(buf, "%x %x %x %x %x %x %lx", &vendor, &device, + &mod_vendor, &module, + &class_code, &class_mask, &driver_data); + if (fileds < 2) /* 2 parameter for vender & device id */ + return -EINVAL; + + if (fileds != 7) { /* 7 for driver_data input */ + ret = ub_check_id_exist(udrv, vendor, device, mod_vendor, + module, class_code); + if (ret) + return ret; + } + + if (udrv->id_table) { + ret = ub_check_driver_data_valid(udrv->id_table, driver_data); + if (ret) + return ret; + } + + ret = ub_add_dynid(udrv, vendor, device, mod_vendor, module, + class_code, class_mask, driver_data); + if (ret) + return ret; + + return count; +} +static DRIVER_ATTR_WO(new_id); + +static ssize_t remove_id_store(struct device_driver *driver, const char *buf, + size_t count) +{ + u32 vendor, device, class_code = 0, class_mask = 0; + u32 mod_vendor = UB_ANY_ID, module = UB_ANY_ID; + struct ub_driver *udrv = to_ub_driver(driver); + struct ub_dynid *dynid, *n; + ssize_t ret = -ENODEV; + int fields; + + fields = sscanf(buf, "%x %x %x %x %x %x", &vendor, &device, &mod_vendor, + &module, &class_code, &class_mask); + if (fields < 2) /* 2 parameter for vender & device id */ + return -EINVAL; + + spin_lock(&udrv->dynids.lock); + list_for_each_entry_safe(dynid, n, &udrv->dynids.list, node) { + struct ub_device_id *id = &dynid->id; + + if ((id->vendor == vendor) && + (id->device == device) && + (mod_vendor == UB_ANY_ID || id->mod_vendor == mod_vendor) && + (module == UB_ANY_ID || id->module == module) && + !((id->class_code ^ class_code) & class_mask)) { + list_del(&dynid->node); + kfree(dynid); + ret = (ssize_t)count; + break; + } + } + spin_unlock(&udrv->dynids.lock); + + return ret; +} +static DRIVER_ATTR_WO(remove_id); + +static struct attribute *ub_drv_attrs[] = { + &driver_attr_new_id.attr, + &driver_attr_remove_id.attr, + NULL +}; +ATTRIBUTE_GROUPS(ub_drv); + +static int ub_bus_num_ue(struct device *dev) +{ + return ub_num_ue(to_ub_entity(dev)); +} + +static void ub_bus_type_init(void) +{ + ub_bus_type.match = ub_bus_match; + ub_bus_type.uevent = ub_uevent; + ub_bus_type.probe = ub_entity_probe; + ub_bus_type.remove = ub_entity_remove; + ub_bus_type.shutdown = ub_entity_shutdown; + ub_bus_type.dev_groups = ub_entity_groups; + ub_bus_type.bus_groups = ub_bus_groups; + ub_bus_type.drv_groups = ub_drv_groups; + ub_bus_type.num_vf = ub_bus_num_ue; +} + +static void ub_bus_type_uninit(void) +{ + ub_bus_type.match = NULL; + ub_bus_type.uevent = NULL; + ub_bus_type.probe = NULL; + ub_bus_type.remove = NULL; + ub_bus_type.shutdown = NULL; + ub_bus_type.dev_groups = NULL; + ub_bus_type.bus_groups = NULL; + ub_bus_type.drv_groups = NULL; + ub_bus_type.num_vf = NULL; +} + +static int ub_service_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ub_service_device *sdev; + struct ub_service_driver *sdrv; + + if (drv->bus != &ub_service_bus_type || dev->bus != &ub_service_bus_type) + return 0; + + sdev = to_ub_service_device(dev); + sdrv = to_ub_service_driver(drv); + if (sdrv->service != sdev->service) + return 0; + + return 1; +} + +struct bus_type ub_service_bus_type = { + .name = "ub_service", + .match = ub_service_bus_match, +}; + +static void ubus_driver_resource_drain(void) +{ + ub_dynamic_bus_instance_drain(); + ub_static_cluster_instance_drain(); +} + +static int ub_host_probe(void) +{ + int ret; + + ub_bus_type_init(); + ret = ub_cfg_ops_init(); + if (ret) + goto ub_cfg_ops_init_fail; + + ret = ub_bus_controllers_probe(); + if (ret) + goto ubcs_probe_fail; + + ret = ub_enum_probe(); + if (ret) + goto ub_enum_probe_fail; + + /* + * Now ub_bus_type build-in, bus_attr_groups will not created, + * so init it here. + */ + ret = ub_bus_attr_dynamic_init(); + if (ret) + goto ub_bus_attr_dynamic_init_fail; + + ret = bus_register(&ub_service_bus_type); + if (ret) + goto bus_register_fail; + + ret = ub_services_init(); + if (ret) + goto ub_services_init_fail; + + ret = ub_cdev_init(); + if (ret) + goto cdev_fail; + + if (!manage_subsystem_ops || !manage_subsystem_ops->ras_handler_probe) { + ret = -EFAULT; + goto error_register_fail; + } + + ret = manage_subsystem_ops->ras_handler_probe(); + if (ret) + goto error_register_fail; + + ret = message_rx_init(); + if (ret) + goto message_init_fail; + + return 0; + +message_init_fail: + if (manage_subsystem_ops && manage_subsystem_ops->ras_handler_remove) + manage_subsystem_ops->ras_handler_remove(); +error_register_fail: + ub_cdev_uninit(); +cdev_fail: + ub_services_exit(); +ub_services_init_fail: + bus_unregister(&ub_service_bus_type); +bus_register_fail: + ub_bus_attr_dynamic_uninit(); +ub_bus_attr_dynamic_init_fail: + ub_enum_remove(); +ub_enum_probe_fail: + ub_bus_controllers_remove(); +ubcs_probe_fail: + unregister_ub_cfg_ops(); +ub_cfg_ops_init_fail: + ub_bus_type_uninit(); + return ret; +} + +static void ub_host_remove(void) +{ + message_rx_uninit(); + if (manage_subsystem_ops && manage_subsystem_ops->ras_handler_remove) + manage_subsystem_ops->ras_handler_remove(); + ub_cdev_uninit(); + ub_services_exit(); + bus_unregister(&ub_service_bus_type); + ub_bus_attr_dynamic_uninit(); + ubus_driver_resource_drain(); + ub_enum_remove(); + ub_bus_controllers_remove(); + unregister_ub_cfg_ops(); + ub_bus_type_uninit(); +} + +int register_ub_manage_subsystem_ops(const struct ub_manage_subsystem_ops *ops) +{ + struct ub_bus_controller *ubc; + int ret; + + if (!ops) { + pr_err("ub manage subsystem ops is NULL\n"); + return -EINVAL; + } + + mutex_lock(&manage_subsystem_ops_mutex); + if (!manage_subsystem_ops) { + list_for_each_entry(ubc, &ubc_list, node) { + if (((ubc->attr.ubc_guid_high >> UBC_GUID_VENDOR_SHIFT) & + UBC_GUID_VENDOR_MASK) == ops->vendor) { + manage_subsystem_ops = ops; + ret = ub_host_probe(); + if (ret) + manage_subsystem_ops = NULL; + else + pr_info("ub manage subsystem ops register successfully\n"); + + mutex_unlock(&manage_subsystem_ops_mutex); + return ret; + } + } + pr_warn("ub manage subsystem ops is not match with any of ub controller\n"); + } else { + pr_warn("ub manage subsystem ops has been registered\n"); + } + mutex_unlock(&manage_subsystem_ops_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(register_ub_manage_subsystem_ops); + +void unregister_ub_manage_subsystem_ops(const struct ub_manage_subsystem_ops *ops) +{ + if (!ops) { + pr_err("ub manage subsystem ops is NULL\n"); + return; + } + + mutex_lock(&manage_subsystem_ops_mutex); + if (manage_subsystem_ops == ops) { + ub_host_remove(); + manage_subsystem_ops = NULL; + pr_info("ub manage subsystem ops unregister successfully\n"); + } else { + pr_warn("ub manage subsystem ops is not registered by this vendor\n"); + } + mutex_unlock(&manage_subsystem_ops_mutex); +} +EXPORT_SYMBOL_GPL(unregister_ub_manage_subsystem_ops); + +static int __init ubus_driver_init(void) +{ + pr_info("Ubus driver init successfully.\n"); + return 0; +} + +static void __exit ubus_driver_exit(void) +{ + pr_info("Ubus driver exit successfully.\n"); +} +module_init(ubus_driver_init); +module_exit(ubus_driver_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("UB bus driver"); +MODULE_IMPORT_NS(UB_UBFI); +MODULE_IMPORT_NS(UB_UBUS); diff --git a/drivers/ub/ubus/ubus_driver.h b/drivers/ub/ubus/ubus_driver.h new file mode 100644 index 0000000000000000000000000000000000000000..b2eab906fa31b03872d5706f590cae5739b8bba6 --- /dev/null +++ b/drivers/ub/ubus/ubus_driver.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UBUS_DRIVER_H__ +#define __UBUS_DRIVER_H__ + +extern struct rw_semaphore ub_bus_sem; +extern struct bus_type ub_service_bus_type; + +#endif /* __UBUS_DRIVER_H__ */ diff --git a/drivers/ub/ubus/ubus_entity.c b/drivers/ub/ubus/ubus_entity.c new file mode 100644 index 0000000000000000000000000000000000000000..5cb47a88c1471c9cf9db81e3586dc9ff5c9aa2fe --- /dev/null +++ b/drivers/ub/ubus/ubus_entity.c @@ -0,0 +1,1104 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus entity: " fmt + +#include +#include + +#include "ubus.h" +#include "sysfs.h" +#include "msg.h" +#include "enum.h" +#include "route.h" +#include "port.h" +#include "eid.h" +#include "cna.h" +#include "resource.h" +#include "memory.h" +#include "ubus_controller.h" +#include "ubus_driver.h" +#include "ubus_inner.h" +#include "cap.h" +#include "eu.h" +#include "instance.h" +#include "ubus_entity.h" + +/* + * Entity lifecycle + * + * init process: {ub_alloc_ent -> ub_setup_ent -> ub_entity_add -> ub_start_ent} + * + * uninit process: {ub_stop_ent -> ub_remove_ent} + */ + +#define UENT_NUM_START 1 +#define UENT_NUM_END 0xfffff +#define UB_DEFAULT_MAX_SEG_SIZE SZ_64K + +struct ub_entity *ub_alloc_ent(void) +{ + struct ub_entity *uent; + + uent = kzalloc(sizeof(*uent), GFP_KERNEL); + if (!uent) + return NULL; + + INIT_LIST_HEAD(&uent->node); + INIT_LIST_HEAD(&uent->mue_list); + INIT_LIST_HEAD(&uent->ue_list); + INIT_LIST_HEAD(&uent->cna_list); + INIT_LIST_HEAD(&uent->slot_list); + INIT_LIST_HEAD(&uent->instance_node); + + mutex_init(&uent->active_mutex); + + uent->dev.type = &ub_dev_type; + uent->cna = 0; + uent->tid = 0; /* default tid according to ummu */ + ub_entity_assign_priv_flag(uent, UB_ENTITY_DETACHED, true); + + return uent; +} +EXPORT_SYMBOL_GPL(ub_alloc_ent); + +static DEFINE_IDA(uent_num_ida); +static void ub_entity_num_free(struct ub_entity *uent) +{ + ida_free(&uent_num_ida, uent->uent_num); +} + +static int ub_entity_num_alloc(void) +{ + return ida_alloc_range(&uent_num_ida, UENT_NUM_START, UENT_NUM_END, + GFP_KERNEL); +} + +static int ub_get_guid(struct ub_entity *uent) +{ + char buf[SZ_64] = {}; + struct ub_guid guid; + u16 code; + int ret; + + if (is_primary(uent) && !uent->pool) + return 0; + + if (!uent->pool || uent_type(uent->pue) == UB_TYPE_ICONTROLLER) { + uent_type(uent) = uent_type(uent->pue); + ret = ub_cfg_read_guid(uent, uent->guid.dw); + if (ret) + return ret; + } + + if (uent->pool && uent_type(uent) == UB_TYPE_CONTROLLER) { + ret = ub_cfg_read_guid(uent, guid.dw); + if (ret) + return ret; + + if (!guid_equal(&uent->guid.id, &guid.id)) { + (void)ub_show_guid(&guid, buf); + ub_err(uent, "pool pld guid is not equal entity guid %s\n", + buf); + return -EINVAL; + } + } + + ret = ub_cfg_read_word(uent, UB_CLASS_CODE, &code); + if (ret) + return ret; + + uent->class_code = code; + ub_entity_type_init(uent); + if (!ub_type_valid(uent, false)) + return -EINVAL; + return 0; +} + +static void ub_config_upi(struct ub_entity *uent) +{ + struct device *dev; + int ret; + u16 upi; + + if (is_p_device(uent)) + return; + + if (is_ibus_controller(uent) && uent->ubc->cluster) { + dev = &uent->ubc->dev; + ret = ub_cfg_read_word(uent, UB_UPI, &upi); + if (ret) { + dev_err(dev, "update cluster upi failed, ret=%d\n", ret); + return; + } + + upi &= UB_UPI_MASK; + if (upi) { + dev_info(dev, "update cluster ubc upi, upi=%#x\n", upi); + uent->upi = upi; + } + return; + } + + ret = ub_cfg_write_dword(uent, UB_UPI, uent->upi); + if (ret) + ub_err(uent, "cfg upi failed, ret=%d\n", ret); +} + +static void ub_res_space_prepare(struct ub_entity *pue) +{ + u32 source_support_map[MAX_UB_RES_NUM] = { UB_ERS0S_SUPPORT, + UB_ERS1S_SUPPORT, + UB_ERS2S_SUPPORT }; + u32 support_feature = 0; + int i; + + if (ub_cfg_read_dword(pue, UB_CFG1_SUPPORT_FEATURE_L, &support_feature)) { + ub_err(pue, "read cfg1 support feature failed\n"); + return; + } + + if (is_idev(pue) || is_ibus_controller(pue)) { + if (!(support_feature & UB_UBBAS_SUPPORT)) + return; + + for (i = 0; i < MAX_UB_RES_NUM; i++) + if (support_feature & source_support_map[i]) + pue->zone[i].ubba_used = 1; + return; + } + + for (i = 0; i < MAX_UB_RES_NUM; i++) { + if (!(support_feature & source_support_map[i])) + continue; + + pue->zone[i].sa_used = 1; + if (is_p_device(pue)) + pue->zone[i].res.flags = IORESOURCE_MEM; + } +} + +static int ub_setup_ent_primary(struct ub_entity *uent) +{ + int ret; + + if (uent->ent_type == UB_ENT_UNKNOWN) { + ub_err(uent, "ignore unknown entity type, type=%#x, class=%#x\n", + uent_type(uent), uent_class(uent)); + return -EIO; + } + + ub_res_space_prepare(uent); + + if (is_p_device(uent)) + return 0; + + ret = ub_cfg_read_word(uent, UB_TOTAL_NUMBER_ENTITIES, + (u16 *)&uent->total_funcs); + if (ret) { + ub_err(uent, "get UB_TOTAL_NUMBER_ENTITIES failed, ret=%d\n", ret); + return ret; + } + + if (!uent->total_funcs) { + ub_err(uent, "total_funcs is zero\n"); + return -EINVAL; + } + /* Number of Entities presented to users, except Entity0. */ + uent->total_funcs -= 1; + + return 0; +} + +static int ub_setup_ent_normal(struct ub_entity *uent) +{ + uent->dev.parent = &uent->pue->dev; + return 0; +} + +static int ub_uent_cfg(struct ub_entity *uent, u32 uent_num) +{ + struct ub_guid *guid = &uent->guid; + char buf[SZ_64] = {}; + int ret; + + dev_set_name(&uent->dev, "%05x", uent_num); + uent->uent_num = uent_num; + + (void)ub_show_guid(guid, buf); + ub_info(uent, "guid=%s, uent_num=%#05x\n", buf, uent_num); + + uent->dev.bus = &ub_bus_type; + /* Card driver set to 64bit if support */ + uent->dma_mask = GENMASK(31, 0); + + if (is_primary(uent) || is_p_device(uent)) + ret = ub_setup_ent_primary(uent); + else + ret = ub_setup_ent_normal(uent); + + return ret; +} + +static void ub_config_eid(struct ub_entity *uent) +{ + if (is_p_device(uent)) + return; + + if (is_ibus_controller(uent) && uent->ubc->cluster) + return; + + ub_cfg_write_dword(uent, UB_EID_0, uent->eid); +} + +static void ub_set_fm_info(struct ub_entity *uent) +{ + if (uent->entity_idx) + return; + + if (uent->ubc->cluster && !is_idev(uent)) + return; + + ub_cfg_write_dword(uent, UB_FM_CNA, uent->ubc->uent->cna); + ub_cfg_write_dword(uent, UB_FM_EID_0, uent->ubc->uent->eid); +} + +static void ub_get_module_id(struct ub_entity *uent) +{ + int ret; + u32 val; + + ret = ub_cfg_read_dword(uent, UB_MODULE, &val); + if (ret) { + ub_err(uent, "Get dev module failed %d\n", ret); + return; + } + + uent->mod_vendor = val >> SZ_16; + uent->module = val & UB_MODULE_ID_MASK; +} + +int ub_setup_ent(struct ub_entity *uent) +{ + int ret, uent_num; + + if (!uent) + return -EINVAL; + + ret = message_probe_device(uent); + if (ret) { + ub_err(uent, "probe message failed, ret=%d\n", ret); + return ret; + } + + ret = ub_get_guid(uent); + if (ret) { + ub_err(uent, "get guid failed, ret=%d\n", ret); + goto err_alloc; + } + + ub_config_upi(uent); + + /* common setup */ + ret = ub_eid_alloc(uent); + if (ret) { + ub_err(uent, "alloc eid failed, ret=%d\n", ret); + goto err_alloc; + } + + uent_num = ub_entity_num_alloc(); + if (uent_num < 0) { + ub_err(uent, "alloc dev uent_num failed, ret=%d\n", uent_num); + ret = -ENOSPC; + goto free_eid; + } + + ret = ub_uent_cfg(uent, (u32)uent_num); + if (ret) + goto free_uent_num; + + ub_config_eid(uent); + ub_set_cap_bitmap(uent); + ub_set_fm_info(uent); + ub_get_module_id(uent); + + ub_entity_assign_priv_flag(uent, UB_ENTITY_SETUP, true); + return 0; +free_uent_num: + ub_entity_num_free(uent); +free_eid: + ub_eid_free(uent); +err_alloc: + message_remove_device(uent); + return ret; +} +EXPORT_SYMBOL_GPL(ub_setup_ent); + +static void ub_configure_ent(struct ub_entity *uent) +{ + ub_eu_table_init(uent); +} + +static void ub_unconfigure_ent(struct ub_entity *uent) +{ + ub_eu_table_uninit(uent); +} + +static int ub_ue_sort_by_ent_idx(void *priv, const struct list_head *a, + const struct list_head *b) +{ + struct ub_entity *uent_a = container_of(a, struct ub_entity, node); + struct ub_entity *uent_b = container_of(b, struct ub_entity, node); + + return uent_a->entity_idx > uent_b->entity_idx; +} + +static void ub_release_ent(struct device *dev); +void ub_entity_add(struct ub_entity *uent, void *ctx) +{ + struct ub_bus_controller *ubc; + struct list_head *list; + struct ub_entity *pue; + u8 need_sort = 0; + int ret, node; + + if (!uent || !ctx) + return; + + ret = ub_entity_setup_mmio(uent); + WARN_ON(ret); + + ub_configure_ent(uent); + + device_initialize(&uent->dev); + uent->dev.release = ub_release_ent; + + uent->dev.dma_mask = &uent->dma_mask; + uent->dev.dma_parms = &uent->dma_parms; + uent->dev.coherent_dma_mask = GENMASK_ULL(31, 0); + dma_set_max_seg_size(&uent->dev, UB_DEFAULT_MAX_SEG_SIZE); + dma_set_seg_boundary(&uent->dev, GENMASK(31, 0)); + + uent->state_saved = false; + ub_init_capabilities(uent); + + if (is_primary(uent) || is_p_device(uent)) { + ubc = (struct ub_bus_controller *)ctx; + node = ub_ubc_to_node(ubc); + list = &ubc->devs; + } else if (uent->is_mue) { + pue = (struct ub_entity *)ctx; + node = dev_to_node(&pue->dev); + list = &pue->mue_list; + } else { + pue = (struct ub_entity *)ctx; + node = dev_to_node(&pue->dev); + list = &pue->ue_list; + need_sort = 1; + } + + set_dev_node(&uent->dev, node); + ub_entity_assign_priv_flag(uent, UB_ENTITY_DETACHED, false); + + down_write(&ub_bus_sem); + list_add_tail(&uent->node, list); + if (need_sort) + list_sort(NULL, list, ub_ue_sort_by_ent_idx); + up_write(&ub_bus_sem); + + dev_set_msi_domain(&uent->dev, uent->ubc->dev.msi.domain); + + uent->match_driver = false; + ret = device_add(&uent->dev); + WARN_ON(ret < 0); + + if (is_primary(uent)) { + ret = ub_ports_add(uent); + WARN_ON(ret); + } + + if (is_ibus_controller(uent)) { + ret = ub_static_bus_instance_init(uent->ubc); + WARN_ON(ret); + } +} +EXPORT_SYMBOL_GPL(ub_entity_add); + +static int ub_mue_enable_and_map(struct ub_entity *uent); +void ub_start_ent(struct ub_entity *uent) +{ + int ret; + + if (!uent) + return; + + ret = ub_default_bus_instance_init(uent); + if (ret) { + ub_err(uent, "default bi init failed, ret=%d\n", ret); + return; + } + + ub_create_sysfs_dev_files(uent); + ub_mem_decoder_init(uent); + + if (!((is_p_device(uent) || is_p_idevice(uent)) && is_dynamic(uent->bi))) { + uent->match_driver = true; + ret = device_attach(&uent->dev); + if (ret < 0 && ret != -EPROBE_DEFER) + ub_err(uent, "device attach failed, ret=%d\n", ret); + } + + if (is_primary(uent) && !is_p_device(uent)) { + ret = ub_mue_enable_and_map(uent); + if (ret) + ub_err(uent, "enable pue failed, ret=%d\n", ret); + } + + ub_entity_assign_priv_flag(uent, UB_ENTITY_START, true); +} +EXPORT_SYMBOL_GPL(ub_start_ent); + +static void ub_release_ent(struct device *dev) +{ + struct ub_entity *uent; + u32 uent_num; + + uent = to_ub_entity(dev); + if (is_primary(uent) && !is_p_device(uent)) { + ub_route_clear(uent); + ub_cna_free(uent); + ub_ports_unset(uent); + } + + message_remove_device(uent); + + if (is_primary(uent) || (is_p_device(uent) && uent->is_mue)) + ub_ubc_put(uent->ubc); + else + ub_entity_put(uent->pue); + + kfree(uent->driver_override); + uent->token_value = 0; + uent_num = uent->uent_num; + kfree(uent); + pr_info("uent[%#x] release\n", uent_num); +} + +void ub_stop_ent(struct ub_entity *uent) +{ + struct ub_entity *ent, *tmp; + + if (!uent) + return; + + if (!ub_entity_test_priv_flag(uent, UB_ENTITY_START)) + return; + ub_entity_assign_priv_flag(uent, UB_ENTITY_START, false); + + /* Stop ue in mue, when uent is not mue, ue_list is NULL */ + list_for_each_entry_safe_reverse(ent, tmp, &uent->ue_list, node) + ub_stop_ent(ent); + /* Stop mue in primary dev, when uent is entN, mue_list is NULL */ + list_for_each_entry_safe_reverse(ent, tmp, &uent->mue_list, node) + ub_stop_ent(ent); + + device_release_driver(&uent->dev); + uent->match_driver = false; + ub_remove_sysfs_ent_files(uent); + + ub_default_bus_instance_uninit(uent); +} +EXPORT_SYMBOL_GPL(ub_stop_ent); + +void ub_remove_ent(struct ub_entity *uent) +{ + struct ub_entity *ent, *tmp; + + if (!uent->dev.kobj.parent) + return; + + /* Remove ue in mue, when uent is not mue, ue_list is NULL */ + list_for_each_entry_safe_reverse(ent, tmp, &uent->ue_list, node) + ub_remove_ent(ent); + /* Remove mue in primary dev, when uent is entN, mue_list is NULL */ + list_for_each_entry_safe_reverse(ent, tmp, &uent->mue_list, node) + ub_remove_ent(ent); + + if (is_ibus_controller(uent)) + ub_static_bus_instance_uninit(uent->ubc); + + if (is_primary(uent)) + ub_ports_del(uent); + + device_del(&uent->dev); + down_write(&ub_bus_sem); + list_del(&uent->node); + up_write(&ub_bus_sem); + + ub_mem_decoder_uninit(uent); + ub_uninit_capabilities(uent); + ub_unconfigure_ent(uent); + ub_entity_unset_mmio(uent); + ub_entity_num_free(uent); + ub_eid_free(uent); + ub_entity_assign_priv_flag(uent, UB_ENTITY_SETUP, false); + + put_device(&uent->dev); +} + +void ub_stop_and_remove_ent(struct ub_entity *uent) +{ + if (!uent) + return; + + ub_stop_ent(uent); + ub_remove_ent(uent); +} +EXPORT_SYMBOL_GPL(ub_stop_and_remove_ent); + +void ub_stop_entities(void) +{ + struct ub_bus_controller *ubc; + struct ub_entity *uent; + + list_for_each_entry_reverse(ubc, &ubc_list, node) + list_for_each_entry_reverse(uent, &ubc->devs, node) + ub_stop_ent(uent); +} + +void ub_remove_entities(void) +{ + struct ub_bus_controller *ubc; + struct ub_entity *uent, *tmp; + + list_for_each_entry_reverse(ubc, &ubc_list, node) + list_for_each_entry_safe_reverse(uent, tmp, &ubc->devs, node) + ub_remove_ent(uent); +} + +struct entity_info_msg_pld_rsp { + u32 reserved; + u16 entity_nums; + u16 mue_nums; + struct ue_map map[]; +}; +#define ENTITY_INFO_BASE_PLD_SIZE 8 + +struct entity_info_msg_pld { + /* request payload is NULL */ + struct entity_info_msg_pld_rsp rsp; +}; + +struct entity_info_msg_pkt { + struct msg_pkt_header header; + struct entity_info_msg_pld pld; +}; + +static int ub_obtain_entity_info_rsp_handle(struct ub_entity *uent, + struct entity_info_msg_pkt *pkt, u16 *mue_nums, + struct ue_map *map) +{ + struct msg_extended_header *etah = &pkt->header.msgetah; + struct entity_info_msg_pld_rsp *rsp = &pkt->pld.rsp; + size_t size; + u16 i; + + if (etah->rsp_status != UB_MSG_RSP_SUCCESS) { + ub_err(uent, "obtain entity info rsp, status=%#02x\n", + etah->rsp_status); + return -EINVAL; + } + + if (etah->plen < ENTITY_INFO_BASE_PLD_SIZE) { + ub_err(uent, "obtain entity info plen=%#x invalid\n", + etah->plen); + return -EINVAL; + } + + uent->total_funcs = rsp->entity_nums; + *mue_nums = rsp->mue_nums; + + size = sizeof(struct ue_map) * (*mue_nums); + if (*mue_nums == 0 || size > SZ_1K) { + ub_err(uent, "mue_nums or size error, mue_nums=%#x, size=%#lx\n", + *mue_nums, size); + return -EINVAL; + } + + if (etah->plen != (*mue_nums * SZ_4 + ENTITY_INFO_BASE_PLD_SIZE)) { + ub_err(uent, "obtain entity info plen=%#x, mue_nums=%#x invalid\n", + etah->plen, *mue_nums); + return -EINVAL; + } + + for (i = 0; i < *mue_nums; i++) { + map[i].start_entity_idx = rsp->map[i].start_entity_idx; + map[i].end_entity_idx = rsp->map[i].end_entity_idx; + } + + return 0; +} + +static int ub_obtain_entity_info(struct ub_entity *uent, u16 *mue_nums, + struct ue_map *map) +{ + struct message_device *mdev = uent->message->mdev; + struct entity_info_msg_pkt req_pkt = {}; + struct entity_info_msg_pkt *rsp_pkt; + struct msg_info info = {}; + int ret; + + if (is_ibus_controller(uent)) { + *mue_nums = 1; + map->start_entity_idx = 0; + map->end_entity_idx = 0; + return 0; + } + + ub_msg_pkt_header_init(&req_pkt.header, uent, 0, + code_gen(UB_MSG_CODE_EXCH, UB_OBTAIN_ENTITY_INFO, + MSG_REQ), false); + + rsp_pkt = kzalloc(SZ_2K, GFP_KERNEL); + if (!rsp_pkt) + return -ENOMEM; + + message_info_init( + &info, uent, &req_pkt, rsp_pkt, + (sizeof(struct msg_pkt_header) << MSG_REQ_SIZE_OFFSET) | SZ_2K); + + ret = message_sync_request(mdev, &info, req_pkt.header.msgetah.code); + if (ret) + goto out; + + ret = ub_obtain_entity_info_rsp_handle(uent, rsp_pkt, mue_nums, map); +out: + kfree(rsp_pkt); + return ret; +} + +static int ub_enable_mues(struct ub_entity *pue, int nums, struct ue_map *map); +int ub_mue_enable_and_map(struct ub_entity *uent) +{ + struct ue_map *map; + u16 mue_nums; + int ret; + + map = kzalloc(SZ_1K, GFP_KERNEL); + if (!map) + return -ENOMEM; + + ret = ub_obtain_entity_info(uent, &mue_nums, map); + if (ret) + goto out; + + /* add map information to Entity0 */ + uent->uem.start_entity_idx = map[0].start_entity_idx; + uent->uem.end_entity_idx = map[0].end_entity_idx; + uent->total_ues = uent->uem.end_entity_idx - uent->uem.start_entity_idx + 1; + if (uent->entity_idx == map[0].start_entity_idx) + uent->total_ues = 0; + + ret = ub_enable_mues(uent, mue_nums - 1, map); + +out: + kfree(map); + return ret; +} + +static int ub_enable_ent(struct ub_entity *pue, int idx, u8 is_mue, + struct ue_map *map) +{ + int ret; + struct ub_entity *ue; + + ue = ub_alloc_ent(); + if (!ue) + return -ENOMEM; + + ue->entity_idx = idx; + ue->pue = pue; + ue->dev.parent = &pue->dev; + ue->ubc = pue->ubc; + ue->cna = pue->cna; + ue->upi = pue->upi; + + if (is_mue) { + ue->is_mue = is_mue; + ue->uem.start_entity_idx = map->start_entity_idx; + ue->uem.end_entity_idx = map->end_entity_idx; + ue->total_ues = map->end_entity_idx - map->start_entity_idx + 1; + if (ue->entity_idx == map->start_entity_idx) + ue->total_ues = 0; + } + + ret = ub_setup_ent(ue); + if (ret < 0) { + kfree(ue); + return ret; + } + + ub_entity_get(pue); + ub_entity_add(ue, pue); + ub_start_ent(ue); + + return 0; +} + +static void ub_disable_mues(struct ub_entity *pue); + +static int ub_enable_mues(struct ub_entity *pue, int nums, struct ue_map *map) +{ + int ret; + int i; + + /* Enable entities, excluding entity0 */ + for (i = 1; i <= nums; i++) { + ret = ub_enable_ent(pue, i, 1, &map[i]); + if (ret) + goto failed; + } + + return 0; +failed: + ub_disable_mues(pue); + return ret; +} + +void ub_virt_notify(struct ub_entity *pue, u16 entity_idx, bool is_en) +{ + const char *operate = is_en ? "enable" : "disable"; + struct ub_driver *pdrv; + int ret; + + if (pue) { + pdrv = pue->driver; + if (pdrv && pdrv->virt_notify) { + ret = pdrv->virt_notify(pue, entity_idx, is_en); + if (ret) + ub_warn(pue, "drv virt notify %s ue with entity_idx %u failed, ret=%d\n", + operate, entity_idx, ret); + } + } +} + +void ub_disable_ent(struct ub_entity *uent) +{ + struct ub_entity *pue; + bool ue_flag = false; + u16 entity_idx; + + ub_info(uent, "disable entity, eid=%#05x\n", uent->eid); + if (!uent->is_mue) { + pue = uent->pue; + entity_idx = uent->entity_idx; + ue_flag = true; + } + + ub_stop_and_remove_ent(uent); + + if (ue_flag) + ub_virt_notify(pue, entity_idx, false); +} +EXPORT_SYMBOL_GPL(ub_disable_ent); + +static void ub_disable_mues(struct ub_entity *pue) +{ + struct ub_entity *mue, *tmp; + + list_for_each_entry_safe_reverse(mue, tmp, &pue->mue_list, node) + ub_disable_ent(mue); +} + +void ub_disable_ues(struct ub_entity *mue) +{ + struct ub_entity *ue, *tmp; + u16 pool_ues = 0; + + list_for_each_entry_safe_reverse(ue, tmp, &mue->ue_list, + node) { + if (is_p_device(ue) || is_p_idevice(ue)) + pool_ues++; + else + ub_disable_ent(ue); + } + + mue->num_ues = pool_ues; +} + +static int ub_check_ue_para(struct ub_entity *pue, int entity_idx) +{ + if (!pue->is_mue || !pue->total_ues) { + ub_err(pue, "It's not mue or ues 0.\n"); + return -EINVAL; + } + + if (entity_idx < pue->uem.start_entity_idx || entity_idx > pue->uem.end_entity_idx) { + ub_err(pue, "Entity idx is err, start=%d, pre=%d, end=%d.\n", + pue->uem.start_entity_idx, entity_idx, pue->uem.end_entity_idx); + return -EINVAL; + } + + return 0; +} + +/* ub_enable_ue does not support parallel execution */ +int ub_enable_ue(struct ub_entity *pue, int entity_idx) +{ + struct ub_entity *ue; + int ret; + + if (!pue) + return -EINVAL; + + ret = ub_check_ue_para(pue, entity_idx); + if (ret) + return ret; + + list_for_each_entry(ue, &pue->ue_list, node) + if (ue->entity_idx == entity_idx) { + ub_err(ue, "entity_idx[%d] already exists, eid=%#05x\n", + entity_idx, ue->eid); + return -EEXIST; + } + + ret = ub_enable_ent(pue, entity_idx, 0, NULL); + if (ret) + return ret; + + pue->num_ues += 1; + + return ret; +} +EXPORT_SYMBOL_GPL(ub_enable_ue); + +int ub_disable_ue(struct ub_entity *pue, int entity_idx) +{ + struct ub_entity *vd_dev; + int ret; + + if (!pue) + return -EINVAL; + + ret = ub_check_ue_para(pue, entity_idx); + if (ret) + return ret; + + list_for_each_entry(vd_dev, &pue->ue_list, node) + if (vd_dev->entity_idx == entity_idx) { + ub_disable_ent(vd_dev); + pue->num_ues -= 1; + return 0; + } + + ub_err(pue, "No matching entity_idx[%d] found\n", entity_idx); + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(ub_disable_ue); + +void ub_disable_entities(struct ub_entity *uent) +{ + if (!uent) + return; + + if (!uent->is_mue) + return; + + ub_disable_ues(uent); +} +EXPORT_SYMBOL_GPL(ub_disable_entities); + +int ub_num_ue(struct ub_entity *uent) +{ + if (!uent) + return -EINVAL; + + if (!uent->is_mue) + return 0; + + return uent->num_ues; +} +EXPORT_SYMBOL_GPL(ub_num_ue); + +void ub_entity_enable(struct ub_entity *uent, u8 enable) +{ + int ret; + + if (!uent) + return; + + ub_cfg_write_byte(uent, UB_BUS_ACCESS_EN, enable); + ub_cfg_write_byte(uent, UB_ENTITY_RS_ACCESS_EN, enable); + + mutex_lock(&uent->active_mutex); + + if (!enable && !ub_entity_test_priv_flag(uent, UB_ENTITY_ACTIVE)) { + mutex_unlock(&uent->active_mutex); + return; + } + + if (uent->ubc && uent->ubc->ops && uent->ubc->ops->entity_enable) { + ret = uent->ubc->ops->entity_enable(uent, enable); + if (ret) { + mutex_unlock(&uent->active_mutex); + ub_err(uent, "entity enable, ret=%d, enable=%u\n", + ret, enable); + return; + } + } + + ub_info(uent, "Change the entity status to %s\n", enable ? "normal" : "disable"); + + if (enable) + ub_entity_assign_priv_flag(uent, UB_ENTITY_ACTIVE, true); + else + ub_entity_assign_priv_flag(uent, UB_ENTITY_ACTIVE, false); + + mutex_unlock(&uent->active_mutex); +} +EXPORT_SYMBOL_GPL(ub_entity_enable); + +int ub_set_user_info(struct ub_entity *uent) +{ + if (!uent || !uent->ubc || !uent->ubc->uent) + return -EINVAL; + + u32 eid = uent->ubc->uent->eid; + + if (is_p_device(uent) || + (uent->ubc->cluster && is_ibus_controller(uent))) + goto cfg1; + + /* set dsteid to device */ + if (uent->bi) + eid = uent->bi->info.eid; + ub_cfg_write_dword(uent, UB_UEID_0, eid); + ub_cfg_write_dword(uent, UB_UEID_1, 0); + ub_cfg_write_dword(uent, UB_UEID_2, 0); + ub_cfg_write_dword(uent, UB_UEID_3, 0); + ub_cfg_write_dword(uent, UB_UCNA, uent->ubc->uent->cna); + +cfg1: + /* set tid to device */ + ub_cfg_write_dword(uent, UB_ENTITY_TOKEN_ID, uent->tid); + + return 0; +} +EXPORT_SYMBOL_GPL(ub_set_user_info); + +void ub_unset_user_info(struct ub_entity *uent) +{ + if (!uent) + return; + + if (is_p_device(uent) || + (uent->ubc->cluster && is_ibus_controller(uent))) + goto cfg1; + + ub_cfg_write_dword(uent, UB_UCNA, 0); + /* clear eid */ + ub_cfg_write_dword(uent, UB_UEID_0, 0); + ub_cfg_write_dword(uent, UB_UEID_1, 0); + ub_cfg_write_dword(uent, UB_UEID_2, 0); + ub_cfg_write_dword(uent, UB_UEID_3, 0); + +cfg1: + /* clear tid */ + ub_cfg_write_dword(uent, UB_ENTITY_TOKEN_ID, 0); +} +EXPORT_SYMBOL_GPL(ub_unset_user_info); + +static struct ub_entity *ub_get_ue_by_entity_idx(struct ub_entity *pue, u32 entity_idx) +{ + struct ub_entity *ue; + + list_for_each_entry(ue, &pue->ue_list, node) { + if (ue->entity_idx == entity_idx) + return ue; + } + + return NULL; +} + +int ub_activate_entity(struct ub_entity *uent, u32 entity_idx) +{ + struct ub_entity *target_dev; + struct ub_driver *udrv; + int ret; + + if (uent && uent->entity_idx != entity_idx && uent->is_mue) + target_dev = ub_get_ue_by_entity_idx(uent, entity_idx); + else + target_dev = uent; + if (!target_dev) + return -EINVAL; + + udrv = uent->driver; + if (!udrv || !udrv->activate) { + ub_err(uent, "udrv or activate is null\n"); + return -EINVAL; + } + + mutex_lock(&target_dev->active_mutex); + + if (ub_entity_test_priv_flag(target_dev, UB_ENTITY_ACTIVE)) { + ub_warn(uent, "entity_idx[%u] is already in normal state\n", entity_idx); + mutex_unlock(&target_dev->active_mutex); + return 0; + } + + ret = udrv->activate(uent, entity_idx); + if (ret) { + ub_err(uent, "udrv activate entity_idx[%u] failed, ret=%d\n", entity_idx, ret); + } else { + ub_entity_assign_priv_flag(target_dev, UB_ENTITY_ACTIVE, true); + ub_info(uent, "udrv activate entity_idx[%u] success\n", entity_idx); + } + + mutex_unlock(&target_dev->active_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(ub_activate_entity); + +int ub_deactivate_entity(struct ub_entity *uent, u32 entity_idx) +{ + struct ub_entity *target_dev; + struct ub_driver *udrv; + int ret; + + if (uent && uent->entity_idx != entity_idx && uent->is_mue) + target_dev = ub_get_ue_by_entity_idx(uent, entity_idx); + else + target_dev = uent; + if (!target_dev) + return -EINVAL; + + udrv = uent->driver; + if (!udrv || !udrv->deactivate) { + ub_err(uent, "udrv or deactivate is null\n"); + return -EINVAL; + } + + mutex_lock(&target_dev->active_mutex); + + if (!ub_entity_test_priv_flag(target_dev, UB_ENTITY_ACTIVE)) { + ub_warn(uent, "entity_idx[%u] is already in disable state\n", entity_idx); + mutex_unlock(&target_dev->active_mutex); + return 0; + } + + ret = udrv->deactivate(uent, entity_idx); + if (ret) { + ub_err(uent, "udrv deactivate entity_idx[%u] failed, ret=%d\n", entity_idx, ret); + } else { + ub_entity_assign_priv_flag(target_dev, UB_ENTITY_ACTIVE, false); + ub_info(uent, "udrv deactivate entity_idx[%u] success\n", entity_idx); + } + + mutex_unlock(&target_dev->active_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(ub_deactivate_entity); diff --git a/drivers/ub/ubus/ubus_entity.h b/drivers/ub/ubus/ubus_entity.h new file mode 100644 index 0000000000000000000000000000000000000000..aa535e6adf70f2fdd148817c965ffa38e7851403 --- /dev/null +++ b/drivers/ub/ubus/ubus_entity.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __UBUS_ENTITY_H__ +#define __UBUS_ENTITY_H__ + +extern bool entity_flex_en; + +struct ub_entity *ub_alloc_ent(void); +int ub_setup_ent(struct ub_entity *uent); +void ub_entity_add(struct ub_entity *uent, void *ctx); +void ub_start_ent(struct ub_entity *uent); +void ub_remove_ent(struct ub_entity *uent); +void ub_stop_entities(void); +void ub_remove_entities(void); +int ub_num_ue(struct ub_entity *uent); +void ub_disable_ent(struct ub_entity *uent); +void ub_virt_notify(struct ub_entity *pue, u16 entity_idx, bool is_en); + +#endif /* __UBUS_ENTITY_H__ */ diff --git a/drivers/ub/ubus/ubus_inner.h b/drivers/ub/ubus/ubus_inner.h new file mode 100644 index 0000000000000000000000000000000000000000..dd576a6c41a3ff45db1d7aff192d05aba63265fa --- /dev/null +++ b/drivers/ub/ubus/ubus_inner.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __UBUS_INNER_H__ +#define __UBUS_INNER_H__ + +#include + +typedef int (*read_byte_f)(struct ub_entity *uent, u64 pos, u8 *val); +typedef int (*read_word_f)(struct ub_entity *uent, u64 pos, u16 *val); +typedef int (*read_dword_f)(struct ub_entity *uent, u64 pos, u32 *val); +typedef int (*write_byte_f)(struct ub_entity *uent, u64 pos, u8 val); +typedef int (*write_word_f)(struct ub_entity *uent, u64 pos, u16 val); +typedef int (*write_dword_f)(struct ub_entity *uent, u64 pos, u32 val); + +int register_ub_cfg_read_ops(read_byte_f rb, read_word_f rw, read_dword_f rdw); +int register_ub_cfg_write_ops(write_byte_f wb, write_word_f ww, write_dword_f wdw); +void unregister_ub_cfg_ops(void); +struct ub_bus_controller *ub_ubc_get(struct ub_bus_controller *ubc); +void ub_ubc_put(struct ub_bus_controller *ubc); + +#endif /* __UBUS_INNER_H__ */ diff --git a/drivers/ub/ubus/vendor/Kconfig b/drivers/ub/ubus/vendor/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..bd4c3448bf4c77234bfa3f98a05f67984f42c02e --- /dev/null +++ b/drivers/ub/ubus/vendor/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0+ + +menu "UB vendor drivers" + depends on UB_UBUS + +config UB_HISI_UBUS + tristate "hisilicon ubus" + depends on UB_UBUS_BUS + depends on UB_UBUS_USI + default n + help + This option enables the modular part of the hisilicon ubus driver. + If you choose the ubus function provided by HiSilicon say y + and it will be accessible from within Linux. + To compile this driver as a module, choose M here. + Say 'M' here unless you know what you are doing + +endmenu diff --git a/drivers/ub/ubus/vendor/Makefile b/drivers/ub/ubus/vendor/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0a8a19acb2fc3a945416e0dfbd81ef232bcfc812 --- /dev/null +++ b/drivers/ub/ubus/vendor/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-y += hisilicon/ diff --git a/drivers/ub/ubus/vendor/hisilicon/Makefile b/drivers/ub/ubus/vendor/hisilicon/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..fec1dbe157969f5b1c643383ce4998a5bdef6bb2 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ + +hisi_ubus-objs := hisi-ubus.o controller.o vdm.o local-ras.o msg.o msg-core.o +hisi_ubus-objs += msg-debugfs.o eu-table.o memory.o hisi-decoder.o + +obj-$(CONFIG_UB_HISI_UBUS) += hisi_ubus.o diff --git a/drivers/ub/ubus/vendor/hisilicon/controller.c b/drivers/ub/ubus/vendor/hisilicon/controller.c new file mode 100644 index 0000000000000000000000000000000000000000..b9a4e6dc02d0700cff529ade7fda0ae162dc3390 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/controller.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi ctl: " fmt + +#include +#include +#include + +#include "../../ubus_controller.h" +#include "hisi-decoder.h" +#include "hisi-ubus.h" +#include "hisi-msg.h" + +static struct ub_bus_controller_ops hi_ubc_ops = { + .eu_table_init = hi_eu_table_init, + .eu_table_uninit = hi_eu_table_uninit, + .eu_cfg = hi_eu_cfg, + .mem_decoder_create = hi_mem_decoder_create, + .mem_decoder_remove = hi_mem_decoder_remove, + .register_ubmem_irq = hi_register_ubmem_irq, + .unregister_ubmem_irq = hi_unregister_ubmem_irq, + .init_decoder_queue = hi_init_decoder_queue, + .uninit_decoder_queue = hi_uninit_decoder_queue, + .entity_enable = hi_send_entity_enable_msg, + .create_decoder_table = hi_create_decoder_table, + .free_decoder_table = hi_free_decoder_table, + .decoder_map = hi_decoder_map, + .decoder_unmap = hi_decoder_unmap, + .decoder_event_deal = hi_decoder_event_deal, +}; + +static void ub_bus_controller_debugfs_init(struct ub_bus_controller *ubc) +{ + if (!debugfs_initialized()) + return; + + ubc->debug_root = debugfs_create_dir(ubc->name, NULL); +} + +static void ub_bus_controller_debugfs_uninit(struct ub_bus_controller *ubc) +{ + debugfs_remove_recursive(ubc->debug_root); + ubc->debug_root = NULL; +} + +int ub_bus_controller_probe(struct ub_bus_controller *ubc) +{ + int ret; + + ubc->ops = &hi_ubc_ops; + ub_bus_controller_debugfs_init(ubc); + + ret = hi_msg_device_probe(ubc); + if (ret) + goto msg_fail; + + return 0; + +msg_fail: + ub_bus_controller_debugfs_uninit(ubc); + ubc->ops = NULL; + return ret; +} + +void ub_bus_controller_remove(struct ub_bus_controller *ubc) +{ + if (ubc->mdev) + hi_msg_device_remove(ubc); + + ub_bus_controller_debugfs_uninit(ubc); + ubc->ops = NULL; +} diff --git a/drivers/ub/ubus/vendor/hisilicon/eu-table.c b/drivers/ub/ubus/vendor/hisilicon/eu-table.c new file mode 100644 index 0000000000000000000000000000000000000000..b759c372afc0ece3c7e5ec65964635d15eb9be2a --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/eu-table.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi eu: " fmt + +#include +#include "../../ubus.h" +#include "../../msg.h" +#include "../../eu.h" +#include "hisi-msg.h" +#include "hisi-ubus.h" + +enum hi_eu_cfg_msg_code { + EU_CFG_READ, + EU_CFG_WRITE +}; + +enum hi_eu_cfg_mode { + EU_CFG_SERIAL, + EU_CFG_DISCRETE +}; + +enum hi_eu_cfg_status { + EU_CFG_FAIL, + EU_CFG_SUCCESS +}; + +struct hi_eu_cfg_req { + u32 eu_msg_code:4; + u32 cfg_entry_num:10; + u32 tbl_cfg_mode:1; + u32 tbl_cfg_status:1; + u32 entry_start_id:16; + + u32 eid:20; + u32 rsv0:12; + + u32 upi:16; + u32 rsv1:16; +}; +#define HI_EU_CFG_REQ_SIZE 12 + +struct hi_eu_cfg_rsp { + u32 eu_msg_code:4; + u32 cfg_entry_num:10; + u32 tbl_cfg_mode:1; + u32 tbl_cfg_status:1; + u32 entry_start_id:16; +}; +#define HI_EU_CFG_RSP_SIZE 4 + +struct hi_eu_cfg_pld { + union { + struct hi_eu_cfg_req req; + struct hi_eu_cfg_rsp rsp; + }; +}; + +struct hi_eu_table_private { + u32 entries; + unsigned long *bitmap; + u32 *map; + struct mutex lock; +}; + +static int query_bit; + +static int hi_eu_query(struct ub_bus_controller *ubc, int bit, u32 *eid, u16 *upi) +{ + struct hi_eu_cfg_pld pld = {}; + struct msg_info info = {}; + int ret; + + /* + * The format of the query response packet is the same as that of the + * configuration request packet. The format of the query request packet + * is the same as that of the configuration response packet. Therefore, + * the data structure is directly reused. + */ + + pld.req.eu_msg_code = EU_CFG_READ; + pld.req.cfg_entry_num = 1; + pld.req.tbl_cfg_mode = EU_CFG_SERIAL; + pld.req.entry_start_id = (u16)bit; + + message_info_init(&info, ubc->uent, &pld, &pld, + (HI_EU_CFG_RSP_SIZE << MSG_REQ_SIZE_OFFSET) | + HI_EU_CFG_REQ_SIZE); + + ret = hi_message_private(ubc->mdev, &info, EU_TABLE_CFG_CMD); + if (ret || pld.rsp.tbl_cfg_status != EU_CFG_SUCCESS) { + dev_err(&ubc->dev, "eu query bit(%d) failed, ret=%d, status=%u\n", + bit, ret, pld.rsp.tbl_cfg_status); + return ret ? ret : -EBUSY; + } + + *eid = pld.req.eid; + *upi = pld.req.upi; + + return 0; +} + +static ssize_t hi_eu_table_info_read(struct file *file, char __user *ubuf, + size_t size, loff_t *loff) +{ + struct ub_bus_controller *ubc = + (struct ub_bus_controller *)file->f_inode->i_private; + char buf[SZ_64]; + size_t len; + u32 eid; + u16 upi; + int ret; + + ret = hi_eu_query(ubc, query_bit, &eid, &upi); + if (ret) + return ret; + + len = (size_t)scnprintf(buf, sizeof(buf), + "eu query bit %d eid %#05x upi %#04x\n", + query_bit, eid, upi); + + return simple_read_from_buffer(ubuf, size, loff, buf, len); +} + +static ssize_t hi_eu_table_info_write(struct file *file, + const char __user *ubuf, + size_t size, loff_t *loff) +{ + struct ub_bus_controller *ubc = + (struct ub_bus_controller *)file->f_inode->i_private; +#define BUF_SIZE 8 + int len, bit, ret; + char buf[BUF_SIZE]; + + if (*loff != 0) + return 0; + + if (size >= BUF_SIZE) + return -ENOSPC; + + len = simple_write_to_buffer(buf, BUF_SIZE - 1, loff, ubuf, size); + if (len < 0) + return len; + + buf[len] = '\0'; + ret = kstrtoint(buf, 10, &bit); + if (ret < 0) + return ret; + + if (bit >= (int)ubc->uent->eu_table->entries) { + pr_err("eu table query bit(%d) invalid\n", bit); + return -EINVAL; + } + + query_bit = bit; + return size; +} + +static const struct file_operations hi_eu_table_info_ops = { + .read = hi_eu_table_info_read, + .write = hi_eu_table_info_write, +}; + +static void hi_eu_table_debugfs_init(struct ub_bus_controller *ubc) +{ + if (ubc->cluster) + return; + + debugfs_create_file("eu_table", 0600, ubc->debug_root, + ubc, &hi_eu_table_info_ops); +} + +static void hi_eu_table_debugfs_uninit(struct ub_bus_controller *ubc) +{ + if (ubc->cluster) + return; + + debugfs_lookup_and_remove("eu_table", ubc->debug_root); +} + +int hi_eu_table_init(struct ub_bus_controller *ubc) +{ + struct ub_eu_table *t = ubc->uent->eu_table; + struct hi_eu_table_private *p; + size_t size; + + if (t->entries >= INT_MAX) + return -EINVAL; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->entries = t->entries; + + size = BITS_TO_LONGS(p->entries) * sizeof(unsigned long); + p->bitmap = kzalloc(size, GFP_KERNEL); + if (!p->bitmap) + goto free; + + p->map = kcalloc(p->entries, sizeof(u32), GFP_KERNEL); + if (!p->map) + goto free_bitmap; + + mutex_init(&p->lock); + t->private_data = p; + + hi_eu_table_debugfs_init(ubc); + return 0; + +free_bitmap: + kfree(p->bitmap); +free: + kfree(p); + return -ENOMEM; +} + +void hi_eu_table_uninit(struct ub_bus_controller *ubc) +{ + struct ub_eu_table *t = ubc->uent->eu_table; + struct hi_eu_table_private *p; + + hi_eu_table_debugfs_uninit(ubc); + + p = (struct hi_eu_table_private *)t->private_data; + kfree(p->map); + kfree(p->bitmap); + kfree(p); + t->private_data = NULL; +} + +static int hi_eu_get_bit(struct hi_eu_table_private *p) +{ + int bit, ret; + + mutex_lock(&p->lock); + bit = (int)find_first_zero_bit(p->bitmap, p->entries); + if (bit != (int)p->entries) { + set_bit(bit, p->bitmap); + ret = bit; + } else { + ret = -ENOSPC; + } + mutex_unlock(&p->lock); + + return ret; +} + +static void hi_eu_put_bit(struct hi_eu_table_private *p, int bit) +{ + mutex_lock(&p->lock); + clear_bit(bit, p->bitmap); + mutex_unlock(&p->lock); +} + +static int hi_eu_find_bit_and_mask(struct hi_eu_table_private *p, u32 eid) +{ + u32 bit; + + mutex_lock(&p->lock); + for_each_set_bit(bit, p->bitmap, p->entries) + if (p->map[bit] == eid) { + p->map[bit] = 0; + break; + } + mutex_unlock(&p->lock); + + if (bit == p->entries) + return -EINVAL; + + return bit; +} + +int hi_eu_cfg(struct ub_bus_controller *ubc, bool add, u32 eid, u16 upi) +{ + struct ub_eu_table *eu = ubc->uent->eu_table; + struct hi_eu_cfg_pld pld = {}; + struct hi_eu_table_private *p; + struct msg_info info = {}; + struct ub_eu_entry *entry; + int ret, bit; + + if (!eu) + return -EINVAL; + + p = (struct hi_eu_table_private *)eu->private_data; + + if (add) + bit = hi_eu_get_bit(p); + else + bit = hi_eu_find_bit_and_mask(p, eid); + + if (bit < 0) + return bit; + + pld.req.eu_msg_code = EU_CFG_WRITE; + pld.req.cfg_entry_num = 1; + pld.req.tbl_cfg_mode = EU_CFG_SERIAL; + pld.req.entry_start_id = (u16)bit; + pld.req.eid = add ? eid : 0; + pld.req.upi = add ? upi : 0; + + message_info_init(&info, ubc->uent, &pld, &pld, + (HI_EU_CFG_REQ_SIZE << MSG_REQ_SIZE_OFFSET) | + HI_EU_CFG_RSP_SIZE); + + ret = hi_message_private(ubc->mdev, &info, EU_TABLE_CFG_CMD); + if (ret || pld.rsp.tbl_cfg_status != EU_CFG_SUCCESS) { + dev_err(&ubc->dev, "eu cfg failed, eid %#05x upi %#04x, ret=%d, status=%u\n", + eid, upi, ret, pld.rsp.tbl_cfg_status); + if (add) + hi_eu_put_bit(p, bit); + else + p->map[bit] = eid; + return ret ? ret : -EBUSY; + } + + entry = (struct ub_eu_entry *)eu->addr + bit; + + if (add) { + p->map[bit] = eid; + entry->eid[0] = eid; + entry->upi = upi; + } else { + hi_eu_put_bit(p, bit); + entry->eid[0] = 0; + entry->upi = 0; + } + + dev_info(&ubc->dev, "eu cfg bit(%d) type(%d) eid %#05x upi %#04x\n", + bit, add, eid, upi); + + return 0; +} diff --git a/drivers/ub/ubus/vendor/hisilicon/hisi-decoder.c b/drivers/ub/ubus/vendor/hisilicon/hisi-decoder.c new file mode 100644 index 0000000000000000000000000000000000000000..a9616b5195a07943ba4e7a8bdcefce67d79a59ca --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/hisi-decoder.c @@ -0,0 +1,1106 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi decoder: " fmt + +#include +#include +#include +#include "../../ubus.h" +#include "hisi-ubus.h" +#include "hisi-decoder.h" + +enum entry_type { + INVALID_ENTRY = 0x0, + PAGE_TABLE = 0x1, + PAGE = 0x2, + RANGE_TABLE = 0x3, +}; + +struct page_table_entry { + u64 entry_type : 2; + u64 reserve1 : 10; + u64 next_lv_addr : 36; + u64 reserve2 : 8; + u64 pgtlb_attr : 8; +}; + +struct page_entry { + /* dw0 ~ dw1 */ + u64 entry_type : 2; + u64 reserve0 : 6; + u64 token_vld : 1; + u64 reserve1 : 3; + u64 ub_addr : 52; + /* dw2 ~ dw3 */ + u64 reserve2 : 32; + u64 tpg_num : 20; + u64 order_type : 2; + u64 order_id : 8; + u64 reserve3 : 2; + /* dw4 ~ dw5 */ + u64 dst_eid_low; + /* dw6 ~ dw7 */ + u64 dst_eid_high; + /* dw8 ~ dw9 */ + u64 token_id : 20; + u64 reserve4 : 12; + u64 token_value : 32; + /* dw10 ~ dw11 */ + u64 upi : 16; + u64 reserve5 : 48; + /* dw12 ~ dw13 */ + u64 reserve6; + /* dw14 ~ dw15 */ + u64 reserve7 : 32; + u64 src_eid : 20; + u64 reserve8 : 12; +}; + +struct range_table_entry { + /* dw0 ~ dw1 */ + u64 entry_type : 2; + u64 reserve0 : 6; + u64 token_vld : 1; + u64 reserve1 : 3; + u64 next_lv_addr : 36; + u64 reserve2 : 8; + u64 page_table_attr : 8; + /* dw2 ~ dw3 */ + u64 mem_base : 15; + u64 reserve3 : 1; + u64 mem_limit : 15; + u64 reserve4 : 1; + u64 reserve5 : 32; + /* dw4 ~ dw5 */ + u64 reserve6 : 12; + u64 ub_addr : 52; + /* dw6 ~ dw7 */ + u64 reserve7 : 32; + u64 tpg_num : 20; + u64 order_type : 2; + u64 order_id : 8; + u64 reserve8 : 2; + /* dw8 ~ dw9 */ + u64 dst_eid_low; + /* dw10 ~ dw11 */ + u64 dst_eid_high; + /* dw12 ~ dw13 */ + u64 token_id : 20; + u64 reserve9 : 12; + u64 token_value : 32; + /* dw14 ~ dw15 */ + u64 upi : 16; + u64 reserve10 : 16; + u64 src_eid : 20; + u64 reserve11 : 12; +}; + +#define DECODER_PAGE_TABLE_ENTRY_SIZE 8 +#define UBA_ADDR_OFFSET 12 + +#define DECODER_PAGE_INDEX_LOC 20 +#define DECODER_PAGE_TABLE_LOC 35 +#define DECODER_SUB_PAGE_TABLE_LOC 32 +#define DECODER_PAGE_TABLE_MASK GENMASK_ULL(43, 35) +#define DECODER_SUB_PAGE_TABLE_MASK GENMASK_ULL(34, 32) +#define DECODER_PGTLB_PAGE_MASK GENMASK_ULL(31, 20) +#define DECODER_RGTLB_PAGE_MASK GENMASK_ULL(34, 20) +#define DECODER_RGTLB_ADDRESS_MASK GENMASK_ULL(34, 20) +#define DECODER_RGTLB_ADDRESS_OFFSET 20 +#define TOKEN_VALID_MASK GENMASK(0, 0) +#define MEM_LMT_MAX 0x7FFF +#define RANGE_UBA_LOW_MASK GENMASK_ULL(34, 20) +#define RANGE_UBA_HIGH_MASK GENMASK_ULL(63, 35) +#define UBA_CARRY 0x800000000 +#define UBA_NOCARRY 0x0 + +#define get_pgtlb_idx(decoder, pa) ((((pa) - (decoder)->mmio_base_addr) & \ + DECODER_PAGE_TABLE_MASK) >> \ + DECODER_PAGE_TABLE_LOC) + +#define get_rgtlb_addr(decoder, pa) ((((pa) - (decoder)->mmio_base_addr) & \ + DECODER_RGTLB_ADDRESS_MASK) >> \ + DECODER_RGTLB_ADDRESS_OFFSET) + +#define get_rgtlb_page_idx(decoder, pa) ((((pa) - (decoder)->mmio_base_addr) & \ + DECODER_RGTLB_PAGE_MASK) >> \ + DECODER_PAGE_INDEX_LOC) + +#define get_page_idx(decoder, pa) ((((pa) - (decoder)->mmio_base_addr) & \ + DECODER_PGTLB_PAGE_MASK) >> \ + DECODER_PAGE_INDEX_LOC) + +#define get_pgtlb_sub_idx(decoder, pa) ((((pa) - (decoder)->mmio_base_addr) & \ + DECODER_SUB_PAGE_TABLE_MASK) >> \ + DECODER_SUB_PAGE_TABLE_LOC) + +#define DECODER_QUEUE_TIMEOUT_US 1000000 /* 1s */ +#define CMD_ENTRY_SIZE 16 +#define EVT_ENTRY_SIZE 16 + +static void fill_page_entry(struct page_entry *page, + struct decoder_map_info *info, u64 offset) +{ + page->entry_type = PAGE; + page->token_vld = ubc_feature & TOKEN_VALID_MASK; + page->ub_addr = (info->uba + offset) >> UBA_ADDR_OFFSET; + page->tpg_num = info->tpg_num; + page->order_type = info->order_type; + page->order_id = info->order_id; + page->dst_eid_low = info->eid_low; + page->dst_eid_high = info->eid_high; + page->token_id = info->token_id; + page->token_value = info->token_value; + page->upi = info->upi; + page->src_eid = info->src_eid; +} + +static int rgtlb_alloc_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct range_table_entry *entry) +{ + void *page_base; + + page_base = dmam_alloc_coherent(decoder->dev, RANGE_TABLE_PAGE_SIZE, + &desc->page_dma, GFP_KERNEL); + if (!page_base) + return -ENOMEM; + + desc->page_base = page_base; + desc->count = 0; + entry->next_lv_addr = (desc->page_dma & DECODER_PGTBL_PGPRT_MASK) >> + DECODER_DMA_PAGE_ADDR_OFFSET; + entry->page_table_attr = PGTLB_ATTR_DEFAULT; + return 0; +} + +static void rgtlb_free_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct range_table_entry *rgtlb_entry) +{ + memset(desc->page_base, 0, RANGE_TABLE_PAGE_SIZE); + dmam_free_coherent(decoder->dev, RANGE_TABLE_PAGE_SIZE, desc->page_base, + desc->page_dma); + rgtlb_entry->next_lv_addr = decoder->invalid_page_dma; + desc->page_base = NULL; + desc->page_dma = 0; +} + +static int rgtlb_map(struct ub_decoder *decoder, u32 table_idx, + struct decoder_map_info *info, u64 *offset) +{ + struct range_table_entry *rgtlb_entry; + struct ub_entity *uent = decoder->uent; + struct page_table_desc *desc; + struct page_entry *page; + u32 index; + u64 addr; + + rgtlb_entry = (struct range_table_entry *)decoder->pgtlb.pgtlb_base + + table_idx; + desc = decoder->pgtlb.desc_base + table_idx * RGTLB_TO_PGTLB; + addr = get_rgtlb_addr(decoder, info->pa + *offset); + index = get_rgtlb_page_idx(decoder, info->pa + *offset); + if (addr >= rgtlb_entry->mem_base && addr <= rgtlb_entry->mem_limit) { + ub_err(uent, "decoder mapping addr already in range.\n"); + *offset += SZ_1M; + return -EINVAL; + } + + if (!desc->page_base) + if (rgtlb_alloc_page(decoder, desc, rgtlb_entry)) { + *offset += SZ_1M; + return -ENOMEM; + } + + /* + * If the page table is applied for the first time, + * the error branch is not used. + */ + page = (struct page_entry *)desc->page_base + index; + if (page->entry_type != INVALID_ENTRY) { + *offset += SZ_1M; + ub_err(uent, "page entry has been used\n"); + return -EINVAL; + } + + fill_page_entry(page, info, *offset); + desc->count++; + *offset += SZ_1M; + return 0; +} + +static int copy_rg_to_pg(struct ub_decoder *decoder, + struct page_table_desc *desc_base, + struct range_table_entry *rgtlb_entry, + struct page_entry *rg_base, int idx) +{ + struct page_table_entry *pgtlb_entry; + struct page_entry *rentry, *pentry; + struct page_table_desc *desc; + void *page_base; + int i; + + desc = desc_base + idx; + pgtlb_entry = (struct page_table_entry *)rgtlb_entry + idx; + page_base = dmam_alloc_coherent(decoder->dev, PAGE_TABLE_PAGE_SIZE, + &desc->page_dma, GFP_KERNEL); + if (!page_base) + return -ENOMEM; + + desc->page_base = page_base; + desc->count = 0; + pgtlb_entry->entry_type = PAGE_TABLE; + pgtlb_entry->next_lv_addr = (desc->page_dma & + DECODER_PGTBL_PGPRT_MASK) >> + DECODER_DMA_PAGE_ADDR_OFFSET; + pgtlb_entry->pgtlb_attr = PGTLB_ATTR_DEFAULT; + + pentry = (struct page_entry *)page_base; + rentry = (struct page_entry *)rg_base + idx * DECODER_PAGE_SIZE; + for (i = 0; i < DECODER_PAGE_SIZE; i++) { + if ((rentry + i)->entry_type == PAGE) { + desc->count++; + memcpy(pentry + i, rentry + i, DECODER_PAGE_ENTRY_SIZE); + } + } + return 0; +} + +static int rgtlb_unmap_to_range(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct range_table_entry *rgtlb_entry) +{ + struct page_table_entry *pgtlb_entry; + struct page_entry *rg_base; + struct page_entry *entry; + dma_addr_t rg_dma; + int i, j, ret; + bool flag; + + rgtlb_entry->token_value = 0; + for (i = 0; i < RGTLB_TO_PGTLB; i++) { + pgtlb_entry = (struct page_table_entry *)rgtlb_entry + i; + pgtlb_entry->entry_type = PAGE_TABLE; + pgtlb_entry->next_lv_addr = decoder->invalid_page_dma; + pgtlb_entry->pgtlb_attr = PGTLB_ATTR_DEFAULT; + } + + if (!desc->page_base) + return 0; + /* + * If l2 table exists, + * convert l2 table of range table to l2 table of the page table. + */ + rg_base = (struct page_entry *)desc->page_base; + rg_dma = desc->page_dma; + memset(desc, 0, sizeof(struct page_table_desc)); + for (i = 0; i < RGTLB_TO_PGTLB; i++) { + flag = false; + for (j = 0; j < DECODER_PAGE_SIZE; j++) { + entry = rg_base + i * DECODER_PAGE_SIZE + j; + if (entry->entry_type != INVALID_ENTRY) { + flag = true; + break; + } + } + if (flag) { + ret = copy_rg_to_pg(decoder, desc, rgtlb_entry, + rg_base, i); + if (ret) + return ret; + } + } + + memset(rg_base, 0, RANGE_TABLE_PAGE_SIZE); + dmam_free_coherent(decoder->dev, RANGE_TABLE_PAGE_SIZE, rg_base, + rg_dma); + return 0; +} + +static int rgtlb_unmap_to_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct range_table_entry *rgtlb_entry, + u32 index) +{ + struct page_entry *page; + + page = (struct page_entry *)desc->page_base + index; + + if (page->entry_type != PAGE) { + ub_err(decoder->uent, "decoder unmap page from range table error.\n"); + return -EINVAL; + } + page->entry_type = INVALID_ENTRY; + desc->count--; + + if (desc->count == 0) + rgtlb_free_page(decoder, desc, rgtlb_entry); + return 0; +} + +static int rgtlb_unmap(struct ub_decoder *decoder, u32 table_idx, + struct decoder_map_info *info, u64 *offset) +{ + struct range_table_entry *rgtlb_entry; + struct page_table_desc *desc; + u32 sub_idx; + u64 begin; + u64 size; + + rgtlb_entry = (struct range_table_entry *)decoder->pgtlb.pgtlb_base + + table_idx; + desc = decoder->pgtlb.desc_base + table_idx * RGTLB_TO_PGTLB; + + begin = get_rgtlb_addr(decoder, info->pa + *offset); + size = (rgtlb_entry->mem_limit - rgtlb_entry->mem_base + 1ULL) * SZ_1M; + + if (begin == rgtlb_entry->mem_base) { + *offset += size; + if (info->size >= *offset) + return rgtlb_unmap_to_range(decoder, desc, rgtlb_entry); + ub_err(decoder->uent, "can't unmap part of range\n!"); + return -EINVAL; + } + sub_idx = get_rgtlb_page_idx(decoder, info->pa + *offset); + *offset += SZ_1M; + return rgtlb_unmap_to_page(decoder, desc, rgtlb_entry, sub_idx); +} + +static int handle_range_table(struct ub_decoder *decoder, u64 *offset, + struct decoder_map_info *info, bool is_map) +{ + u32 table_idx; + + table_idx = get_pgtlb_idx(decoder, info->pa + *offset); + + if (is_map) + return rgtlb_map(decoder, table_idx, info, offset); + else + return rgtlb_unmap(decoder, table_idx, info, offset); +} + +static int pgtlb_alloc_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct page_table_entry *pgtlb_entry) +{ + void *page_base; + + page_base = dmam_alloc_coherent(decoder->dev, PAGE_TABLE_PAGE_SIZE, + &desc->page_dma, GFP_KERNEL); + if (!page_base) + return -ENOMEM; + + desc->page_base = page_base; + desc->count = 0; + pgtlb_entry->entry_type = PAGE_TABLE; + pgtlb_entry->next_lv_addr = (desc->page_dma & + DECODER_PGTBL_PGPRT_MASK) >> + DECODER_DMA_PAGE_ADDR_OFFSET; + pgtlb_entry->pgtlb_attr = PGTLB_ATTR_DEFAULT; + return 0; +} + +static void pgtlb_free_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct page_table_entry *pgtlb_entry) +{ + memset(desc->page_base, 0, PAGE_TABLE_PAGE_SIZE); + dmam_free_coherent(decoder->dev, PAGE_TABLE_PAGE_SIZE, desc->page_base, + desc->page_dma); + pgtlb_entry->next_lv_addr = decoder->invalid_page_dma; + pgtlb_entry->entry_type = PAGE_TABLE; + desc->page_base = NULL; + desc->page_dma = 0; +} + +static int pgtlb_map_to_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct page_table_entry *pgtlb_entry, + struct decoder_map_info *info, u64 offset) +{ + struct page_entry *page; + u32 index; + int ret; + + if (!desc->page_base) { + ret = pgtlb_alloc_page(decoder, desc, pgtlb_entry); + if (ret) + return ret; + } + + index = get_page_idx(decoder, info->pa + offset); + + page = (struct page_entry *)desc->page_base + index; + + /* + * If it's the first time to apply for a page table, + * the error branch will not be taken. + */ + if (page->entry_type != INVALID_ENTRY) { + ub_err(decoder->uent, "decoder mapping page from page table error.\n"); + return -EINVAL; + } + + fill_page_entry(page, info, offset); + desc->count++; + return 0; +} + +static int pgtlb_unmap_to_page(struct ub_decoder *decoder, + struct page_table_desc *desc, + struct page_table_entry *pgtlb_entry, + struct decoder_map_info *info, u64 offset) +{ + struct page_entry *page; + u32 index; + + index = get_page_idx(decoder, info->pa + offset); + page = (struct page_entry *)desc->page_base + index; + + if (pgtlb_entry->next_lv_addr == decoder->invalid_page_dma || + page->entry_type != PAGE) { + ub_err(decoder->uent, "decoder unmap page from page table error.\n"); + return -EINVAL; + } + + page->entry_type = INVALID_ENTRY; + desc->count--; + + if (desc->count == 0) + pgtlb_free_page(decoder, desc, pgtlb_entry); + return 0; +} + +static int handle_page_table(struct ub_decoder *decoder, u64 *offset, + struct decoder_map_info *info, bool is_map) +{ + struct page_table_entry *pgtlb_entry; + struct page_table_desc *desc; + struct page_table *pgtlb; + u32 table_idx; + u32 sub_index; + int ret; + + pgtlb = &decoder->pgtlb; + table_idx = get_pgtlb_idx(decoder, info->pa + *offset); + sub_index = get_pgtlb_sub_idx(decoder, info->pa + *offset); + desc = pgtlb->desc_base + table_idx * RGTLB_TO_PGTLB + sub_index; + pgtlb_entry = (struct page_table_entry *)pgtlb->pgtlb_base + + table_idx * RGTLB_TO_PGTLB + sub_index; + + if (is_map) + ret = pgtlb_map_to_page(decoder, desc, pgtlb_entry, info, + *offset); + else + ret = pgtlb_unmap_to_page(decoder, desc, pgtlb_entry, info, + *offset); + *offset += SZ_1M; + return ret; +} + +static void fill_range_table(struct ub_decoder *decoder, + struct range_table_entry *rg_entry, + struct decoder_map_info *info, u64 *offset) +{ + u64 mem_base, mem_limit; + u64 uba_high, uba_low; + u64 size; + + mem_base = get_rgtlb_addr(decoder, info->pa + *offset); + size = (MEM_LMT_MAX - mem_base + 1ULL) << DECODER_RGTLB_ADDRESS_OFFSET; + + uba_low = (info->uba + *offset) & RANGE_UBA_LOW_MASK; + uba_high = (info->uba + *offset) & RANGE_UBA_HIGH_MASK; + + if (info->size - *offset >= size) { + mem_limit = MEM_LMT_MAX; + *offset += size; + } else { + mem_limit = get_rgtlb_addr(decoder, info->pa + info->size); + mem_limit--; + *offset += (mem_limit - mem_base + 1) << + DECODER_RGTLB_ADDRESS_OFFSET; + } + + rg_entry->entry_type = RANGE_TABLE; + rg_entry->next_lv_addr = decoder->invalid_page_dma; + rg_entry->page_table_attr = PGTLB_ATTR_DEFAULT; + rg_entry->token_vld = ubc_feature & TOKEN_VALID_MASK; + rg_entry->mem_base = mem_base; + rg_entry->mem_limit = mem_limit; + rg_entry->ub_addr = (uba_high | uba_low) >> UBA_ADDR_OFFSET; + rg_entry->tpg_num = info->tpg_num; + rg_entry->order_id = info->order_id; + rg_entry->order_type = info->order_type; + rg_entry->dst_eid_low = info->eid_low; + rg_entry->dst_eid_high = info->eid_high; + rg_entry->token_id = info->token_id; + rg_entry->token_value = info->token_value; + rg_entry->upi = info->upi; + rg_entry->src_eid = info->src_eid; +} + +static int handle_pgrg_table(struct ub_decoder *decoder, u64 *offset, + struct decoder_map_info *info, bool is_map) +{ + struct range_table_entry *rg_entry; + struct page_table_entry *pg_entry; + u32 table_idx, sub_index; + int i; + + if (!is_map) + return handle_page_table(decoder, offset, info, is_map); + + table_idx = get_pgtlb_idx(decoder, info->pa + *offset); + sub_index = get_pgtlb_sub_idx(decoder, info->pa + *offset); + pg_entry = (struct page_table_entry *)decoder->pgtlb.pgtlb_base + + table_idx * RGTLB_TO_PGTLB; + if (sub_index != 0 || info->size - *offset < decoder->rg_size) + return handle_page_table(decoder, offset, info, is_map); + + for (i = 0; i < RGTLB_TO_PGTLB; i++) + if ((pg_entry + i)->next_lv_addr != decoder->invalid_page_dma) + return handle_page_table(decoder, offset, info, is_map); + + ub_dbg(decoder->uent, "create range table\n"); + rg_entry = (struct range_table_entry *)pg_entry; + fill_range_table(decoder, rg_entry, info, offset); + return 0; +} + +static int handle_table(struct ub_decoder *decoder, + struct decoder_map_info *info, bool is_map) +{ + struct page_table *pgtlb = &decoder->pgtlb; + struct range_table_entry *range_tlb_entry; + u64 offset, rollback_size; + u32 table_idx; + int ret = 0; + + for (offset = 0; offset < info->size;) { + rollback_size = offset; + + mutex_lock(&decoder->table_lock); + table_idx = get_pgtlb_idx(decoder, info->pa + offset); + range_tlb_entry = (struct range_table_entry *) + pgtlb->pgtlb_base + table_idx; + + if (range_tlb_entry->entry_type == RANGE_TABLE) + ret = handle_range_table(decoder, &offset, info, + is_map); + else if (range_tlb_entry->next_lv_addr == + decoder->invalid_page_dma) + ret = handle_pgrg_table(decoder, &offset, info, is_map); + else + ret = handle_page_table(decoder, &offset, info, is_map); + mutex_unlock(&decoder->table_lock); + + if (ret) { + ub_err(decoder->uent, "decoder table occur fatal error ret=%d\n", + ret); + /* if it is map operation, revert it. unmap operation can't revert */ + if (is_map) + (void)hi_decoder_unmap(decoder, info->pa, + rollback_size); + break; + } + } + return ret; +} + +static void ub_decoder_init_page_table(struct ub_decoder *decoder, void *pgtlb_base) +{ + struct page_table_entry *pgtlb_entry; + int i; + + for (i = 0; i < DECODER_PAGE_TABLE_SIZE; i++) { + pgtlb_entry = (struct page_table_entry *)pgtlb_base + i; + pgtlb_entry->entry_type = PAGE_TABLE; + pgtlb_entry->next_lv_addr = decoder->invalid_page_dma; + pgtlb_entry->pgtlb_attr = PGTLB_ATTR_DEFAULT; + } +} + +int hi_init_decoder_queue(struct ub_decoder *decoder) +{ + struct hi_ubc_private_data *data; + struct ub_bus_controller *ubc; + + if (!decoder) + return -EINVAL; + + ubc = decoder->uent->ubc; + data = (struct hi_ubc_private_data *)ubc->data; + decoder->cmdq.base = data->io_decoder_cmdq; + decoder->evtq.base = data->io_decoder_evtq; + + decoder->cmdq.qbase = ioremap(decoder->cmdq.base, + (1 << decoder->cmdq.qs) * CMD_ENTRY_SIZE); + if (!decoder->cmdq.qbase) + return -ENOMEM; + + decoder->evtq.qbase = ioremap(decoder->evtq.base, + (1 << decoder->evtq.qs) * EVT_ENTRY_SIZE); + if (!decoder->evtq.qbase) { + iounmap(decoder->cmdq.qbase); + return -ENOMEM; + } + return 0; +} + +void hi_uninit_decoder_queue(struct ub_decoder *decoder) +{ + iounmap(decoder->cmdq.qbase); + iounmap(decoder->evtq.qbase); +} + +int hi_create_decoder_table(struct ub_decoder *decoder) +{ + struct page_table_desc *invalid_desc = &decoder->invalid_desc; + struct page_table *pgtlb; + void *pgtlb_base; + size_t size; + + size = DECODER_PAGE_TABLE_ENTRY_SIZE * DECODER_PAGE_TABLE_SIZE; + pgtlb = &decoder->pgtlb; + pgtlb_base = dmam_alloc_coherent(decoder->dev, size, + &pgtlb->pgtlb_dma, GFP_KERNEL); + if (!pgtlb_base) + return -ENOMEM; + + pgtlb->pgtlb_base = pgtlb_base; + + size = sizeof(*pgtlb->desc_base) * DECODER_PAGE_TABLE_SIZE; + pgtlb->desc_base = kzalloc(size, GFP_KERNEL); + if (!pgtlb->desc_base) + goto release_pgtlb; + + invalid_desc->page_base = dmam_alloc_coherent(decoder->dev, + RANGE_TABLE_PAGE_SIZE, + &invalid_desc->page_dma, + GFP_KERNEL); + if (!invalid_desc->page_base) + goto release_desc; + + decoder->invalid_page_dma = (invalid_desc->page_dma & + DECODER_PGTBL_PGPRT_MASK) >> + DECODER_DMA_PAGE_ADDR_OFFSET; + + ub_decoder_init_page_table(decoder, pgtlb_base); + + return 0; + +release_desc: + kfree(pgtlb->desc_base); + pgtlb->desc_base = NULL; +release_pgtlb: + size = DECODER_PAGE_TABLE_ENTRY_SIZE * DECODER_PAGE_TABLE_SIZE; + dmam_free_coherent(decoder->dev, size, pgtlb_base, pgtlb->pgtlb_dma); + return -ENOMEM; +} + +void hi_free_decoder_table(struct ub_decoder *decoder) +{ + struct page_table_desc *invalid_desc = &decoder->invalid_desc; + size_t size; + + dmam_free_coherent(decoder->dev, RANGE_TABLE_PAGE_SIZE, + invalid_desc->page_base, invalid_desc->page_dma); + kfree(decoder->pgtlb.desc_base); + + size = DECODER_PAGE_TABLE_ENTRY_SIZE * DECODER_PAGE_TABLE_SIZE; + dmam_free_coherent(decoder->dev, size, decoder->pgtlb.pgtlb_base, + decoder->pgtlb.pgtlb_dma); +} + +int hi_decoder_unmap(struct ub_decoder *decoder, phys_addr_t addr, u64 size) +{ + struct decoder_map_info info = { + .pa = addr, + .size = size, + }; + int ret; + + info.size = ALIGN(info.size, SZ_1M); + ret = handle_table(decoder, &info, false); + if (ret) + return ret; + return hi_decoder_cmd_request(decoder, info.pa, info.size, TLBI_PARTIAL); +} + +int hi_decoder_map(struct ub_decoder *decoder, struct decoder_map_info *info) +{ + info->size = ALIGN(info->size, SZ_1M); + + ub_info(decoder->uent, + "decoder map, pa=%#llx, uba=%#llx, size=%#llx, cna=%#x, orderid=%#x, ordertype=%#x, eid_l=%#llx, eid_h=%#llx, upi=%#x src_eid=%#x\n", + info->pa, info->uba, info->size, info->tpg_num, info->order_id, + info->order_type, info->eid_low, info->eid_high, info->upi, + info->src_eid); + + if (info->pa < decoder->mmio_base_addr || + info->pa + info->size - 1 > decoder->mmio_end_addr || + info->pa > info->pa + info->size - 1) { + pr_err("decoder map range check error\n"); + return -EINVAL; + } + + return handle_table(decoder, info, true); +} + +struct sync_entry { + u64 op : 8; + u64 reserve0 : 4; + u64 cm : 2; + u64 ntf_sh : 2; + u64 ntf_attr : 4; + u64 reserve1 : 12; + u64 notify_data : 32; + u64 reserve2 : 2; + u64 notify_addr : 50; + u64 reserve3 : 12; +}; + +struct tlbi_all_entry { + u32 op : 8; + u32 reserve0 : 24; + u32 reserve1; + u32 reserve2; + u32 reserve3; +}; + +struct tlbi_partial_entry { + u32 op : 8; + u32 reserve0 : 24; + u32 tlbi_addr_base : 28; + u32 reserve1 : 4; + u32 tlbi_addr_limt : 28; + u32 reserve2 : 4; + u32 reserve3; +}; + +#define TLBI_ADDR_MASK GENMASK_ULL(43, 20) +#define TLBI_ADDR_OFFSET 20 +#define CMDQ_ENT_DWORDS 2 + +#define NTF_SH_NSH 0b00 +#define NTF_SH_OSH 0b10 +#define NTF_SH_ISH 0b11 + +#define NTF_ATTR_IR_NC 0b00 +#define NTF_ATTR_IR_WBRA 0b01 +#define NTF_ATTR_IR_WT 0b10 +#define NTF_ATTR_IR_WB 0b11 +#define NTF_ATTR_OR_NC 0b0000 +#define NTF_ATTR_OR_WBRA 0b0100 +#define NTF_ATTR_OR_WT 0b1000 +#define NTF_ATTR_OR_WB 0b1100 + +#define Q_IDX(qs, p) ((p) & ((1 << (qs)) - 1)) +#define Q_WRP(qs, p) ((p) & (1 << (qs))) +#define Q_OVF(p) ((p) & Q_OVERFLOW_FLAG) + +enum NOTIFY_TYPE { + DISABLE_NOTIFY = 0, + ENABLE_NOTIFY = 1, +}; + +static bool queue_has_space(struct ub_decoder_queue *q, u32 n) +{ + u32 space, prod, cons; + + prod = Q_IDX(q->qs, q->prod.cmdq_wr_idx); + cons = Q_IDX(q->qs, q->cons.cmdq_rd_idx); + + if (Q_WRP(q->qs, q->prod.cmdq_wr_idx) == + Q_WRP(q->qs, q->cons.cmdq_rd_idx)) + space = (1 << q->qs) - (prod - cons); + else + space = cons - prod; + + return space >= n; +} + +static u32 queue_inc_prod_n(struct ub_decoder_queue *q, u32 n) +{ + u32 prod = (Q_WRP(q->qs, q->prod.cmdq_wr_idx) | + Q_IDX(q->qs, q->prod.cmdq_wr_idx)) + n; + return Q_WRP(q->qs, prod) | Q_IDX(q->qs, prod); +} + +#define CMD_0_OP GENMASK_ULL(7, 0) +#define CMD_0_ADDR_BASE GENMASK_ULL(59, 32) +#define CMD_1_ADDR_LIMT GENMASK_ULL(27, 0) + +static void decoder_cmdq_issue_cmd(struct ub_decoder *decoder, phys_addr_t addr, + u64 size, enum ub_cmd_op_type op) +{ + struct ub_decoder_queue *cmdq = &(decoder->cmdq); + struct tlbi_partial_entry entry = {}; + u64 cmd[CMDQ_ENT_DWORDS] = {}; + void *pi; + int i; + + entry.op = op; + entry.tlbi_addr_base = (addr & TLBI_ADDR_MASK) >> TLBI_ADDR_OFFSET; + entry.tlbi_addr_limt = ((addr + size - 1U) & TLBI_ADDR_MASK) >> + TLBI_ADDR_OFFSET; + + cmd[0] |= FIELD_PREP(CMD_0_OP, entry.op); + cmd[0] |= FIELD_PREP(CMD_0_ADDR_BASE, entry.tlbi_addr_base); + cmd[1] |= FIELD_PREP(CMD_1_ADDR_LIMT, entry.tlbi_addr_limt); + + pi = cmdq->qbase + Q_IDX(cmdq->qs, cmdq->prod.cmdq_wr_idx) * + sizeof(struct tlbi_partial_entry); + + for (i = 0; i < CMDQ_ENT_DWORDS; i++) + writeq(cmd[i], pi + i * sizeof(u64)); + + cmdq->prod.cmdq_wr_idx = queue_inc_prod_n(cmdq, 1); +} + +#define NTF_DMA_ADDR_OFFSERT 2 +#define SYNC_0_OP GENMASK_ULL(7, 0) +#define SYNC_0_CM GENMASK_ULL(13, 12) +#define SYNC_0_NTF_ISH GENMASK_ULL(15, 14) +#define SYNC_0_NTF_ATTR GENMASK_ULL(19, 16) +#define SYNC_0_NTF_DATA GENMASK_ULL(63, 32) +#define SYNC_1_NTF_ADDR GENMASK_ULL(51, 2) +#define SYNC_NTF_DATA 0xffffffff + +static void decoder_cmdq_issue_sync(struct ub_decoder *decoder) +{ + struct ub_decoder_queue *cmdq = &(decoder->cmdq); + u64 cmd[CMDQ_ENT_DWORDS] = {}; + struct sync_entry entry = {}; + phys_addr_t sync_dma; + void __iomem *pi; + int i; + + entry.op = SYNC; + entry.cm = ENABLE_NOTIFY; + sync_dma = cmdq->base + Q_IDX(cmdq->qs, cmdq->prod.cmdq_wr_idx) * + sizeof(struct sync_entry); + entry.ntf_sh = NTF_SH_NSH; + entry.ntf_attr = NTF_ATTR_IR_NC | NTF_ATTR_OR_NC; + entry.notify_data = SYNC_NTF_DATA; + entry.notify_addr = sync_dma >> NTF_DMA_ADDR_OFFSERT; + + cmd[0] |= FIELD_PREP(SYNC_0_OP, entry.op); + cmd[0] |= FIELD_PREP(SYNC_0_CM, entry.cm); + cmd[0] |= FIELD_PREP(SYNC_0_NTF_ISH, entry.ntf_sh); + cmd[0] |= FIELD_PREP(SYNC_0_NTF_ATTR, entry.ntf_attr); + cmd[0] |= FIELD_PREP(SYNC_0_NTF_DATA, entry.notify_data); + cmd[1] |= FIELD_PREP(SYNC_1_NTF_ADDR, entry.notify_addr); + + pi = cmdq->qbase + Q_IDX(cmdq->qs, cmdq->prod.cmdq_wr_idx) * + sizeof(struct sync_entry); + for (i = 0; i < CMDQ_ENT_DWORDS; i++) + writeq(cmd[i], pi + i * sizeof(u64)); + + decoder->notify = pi; + cmdq->prod.cmdq_wr_idx = queue_inc_prod_n(cmdq, 1); +} + +static void decoder_cmdq_update_prod(struct ub_decoder *decoder) +{ + struct ub_entity *uent = decoder->uent; + struct queue_idx q; + int ret; + + ret = ub_cfg_read_dword(uent, DECODER_CMDQ_PROD, &q.val); + if (ret) + ub_err(uent, "update pi, read decoder cmdq prod fail\n"); + + decoder->cmdq.prod.cmdq_err_resp = q.cmdq_err_resp; + ret = ub_cfg_write_dword(uent, DECODER_CMDQ_PROD, + decoder->cmdq.prod.val); + if (ret) + ub_err(uent, "update pi, write decoder cmdq prod fail\n"); +} + +static int wait_for_cmdq_free(struct ub_decoder *decoder, u32 n) +{ + ktime_t timeout = ktime_add_us(ktime_get(), DECODER_QUEUE_TIMEOUT_US); + struct ub_decoder_queue *cmdq = &(decoder->cmdq); + struct ub_entity *uent = decoder->uent; + int ret; + + while (true) { + ret = ub_cfg_read_dword(uent, DECODER_CMDQ_CONS, + &(cmdq->cons.val)); + if (ret) + return ret; + + if (queue_has_space(cmdq, n + 1)) + return 0; + + if (ktime_compare(ktime_get(), timeout) > 0) { + ub_err(uent, "decoder cmdq wait free entry timeout\n"); + return -ETIMEDOUT; + } + cpu_relax(); + } +} + +static int wait_for_cmdq_notify(struct ub_decoder *decoder) +{ + ktime_t timeout; + u32 val; + + timeout = ktime_add_us(ktime_get(), DECODER_QUEUE_TIMEOUT_US); + while (true) { + val = readl(decoder->notify); + if (val == SYNC_NTF_DATA) + return 0; + + if (ktime_compare(ktime_get(), timeout) > 0) { + ub_err(decoder->uent, "decoder cmdq wait notify timeout\n"); + return -ETIMEDOUT; + } + cpu_relax(); + } +} + +int hi_decoder_cmd_request(struct ub_decoder *decoder, phys_addr_t addr, + u64 size, enum ub_cmd_op_type op) +{ + int ret; + + ret = wait_for_cmdq_free(decoder, 1); + if (ret) + return ret; + + decoder_cmdq_issue_cmd(decoder, addr, size, op); + decoder_cmdq_issue_sync(decoder); + decoder_cmdq_update_prod(decoder); + + ret = wait_for_cmdq_notify(decoder); + return ret; +} +#ifdef UBUS_KP_TOOL_STUB +EXPORT_SYMBOL_GPL(hi_decoder_cmd_request); +#endif + +static bool queue_empty(struct ub_decoder_queue *q) +{ + return (Q_IDX(q->qs, q->prod.eventq_wr_idx) == + Q_IDX(q->qs, q->cons.eventq_rd_idx)) && + (Q_WRP(q->qs, q->prod.eventq_wr_idx) == + Q_WRP(q->qs, q->cons.eventq_rd_idx)); +} + +static void queue_inc_cons(struct ub_decoder_queue *q) +{ + u32 cons = (Q_WRP(q->qs, q->cons.eventq_rd_idx) | + Q_IDX(q->qs, q->cons.eventq_rd_idx)) + 1; + q->cons.eventq_rd_idx = Q_WRP(q->qs, cons) | Q_IDX(q->qs, cons); +} + +enum event_op_type { + RESERVED = 0x0, + EVENT_ADDR_OUT_OF_RANGE = 0x01, + EVENT_ILLEGAL_CMD = 0x02, +}; + +#define EVTQ_0_ID GENMASK_ULL(7, 0) +#define EVTQ_0_ADDR GENMASK_ULL(59, 32) +#define EVTQ_0_CMD_OPCODE GENMASK_ULL(39, 32) +#define EVTQ_ENT_DWORDS 2 +#define MAX_REASON_NUM 3 + +static const char *cmd_err_reason[MAX_REASON_NUM] = { + "no error", + "illegal command", + "abort error(read command with 2bit ecc)" +}; + +static void fix_err_cmd(struct ub_decoder *decoder) +{ + struct ub_decoder_queue *cmdq = &(decoder->cmdq); + struct ub_entity *uent = decoder->uent; + u64 cmd[CMDQ_ENT_DWORDS] = {}; + struct queue_idx prod, cons; + void *pi; + int i; + + if (ub_cfg_read_dword(uent, DECODER_CMDQ_CONS, &cons.val)) { + ub_err(uent, "decoder fix error cmd, read ci failed\n"); + return; + } + if (ub_cfg_read_dword(uent, DECODER_CMDQ_PROD, &prod.val)) { + ub_err(uent, "decoder fix error cmd, read pi failed\n"); + return; + } + + cmd[0] |= FIELD_PREP(CMD_0_OP, TLBI_ALL); + pi = cmdq->qbase + Q_IDX(cmdq->qs, cons.cmdq_rd_idx) * + sizeof(struct tlbi_partial_entry); + + for (i = 0; i < CMDQ_ENT_DWORDS; i++) + writeq(cmd[i], pi + i * sizeof(u64)); + + if (cons.cmdq_err_reason >= MAX_REASON_NUM) + ub_err(uent, "cmdq err reason is invalid, reason=%u\n", + cons.cmdq_err_reason); + else + ub_err(uent, "cmdq err reason is %s\n", cmd_err_reason[cons.cmdq_err_reason]); + + prod.cmdq_err_resp = cons.cmdq_err; + + if (ub_cfg_write_dword(uent, DECODER_CMDQ_PROD, prod.val)) + ub_err(uent, "decoder fix error cmd, write pi err resp failed\n"); +} + +static void handle_evt(struct ub_decoder *decoder, u64 *evt) +{ + struct ub_entity *uent = decoder->uent; + + switch (FIELD_GET(EVTQ_0_ID, evt[0])) { + case EVENT_ADDR_OUT_OF_RANGE: + ub_err(uent, "decoder event, input addr out of range, addr=%#.7x00000\n", + (u32)FIELD_GET(EVTQ_0_ADDR, evt[0])); + break; + case EVENT_ILLEGAL_CMD: + ub_err(uent, "decoder event, illegal cmd, cmd_opcode=%#x\n", + (u32)FIELD_GET(EVTQ_0_CMD_OPCODE, evt[0])); + fix_err_cmd(decoder); + break; + default: + ub_err(uent, "invalid event opcode, opcode=%#x\n", + (u32)FIELD_GET(EVTQ_0_ID, evt[0])); + } +} + +void hi_decoder_event_deal(struct ub_decoder *decoder) +{ + struct ub_decoder_queue *evtq = &decoder->evtq; + struct ub_entity *uent = decoder->uent; + u64 evt[EVTQ_ENT_DWORDS]; + void *ci; + int i; + + if (ub_cfg_read_dword(uent, DECODER_EVENTQ_PROD, &(evtq->prod.val))) { + ub_err(uent, "decoder handle event, read eventq pi fail\n"); + return; + } + + while (!queue_empty(evtq)) { + ci = evtq->qbase + Q_IDX(evtq->qs, evtq->cons.eventq_rd_idx) * + EVT_ENTRY_SIZE; + + for (i = 0; i < EVTQ_ENT_DWORDS; i++) + evt[i] = readq(ci + i * sizeof(u64)); + + handle_evt(decoder, evt); + queue_inc_cons(evtq); + + if (ub_cfg_write_dword(uent, DECODER_EVENTQ_CONS, + evtq->cons.val)) + ub_err(uent, "decoder handle event, write eventq ci fail\n"); + } +} diff --git a/drivers/ub/ubus/vendor/hisilicon/hisi-decoder.h b/drivers/ub/ubus/vendor/hisilicon/hisi-decoder.h new file mode 100644 index 0000000000000000000000000000000000000000..fc49a25b80d6aee0cd335ca5fdefeaa0b08d92fb --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/hisi-decoder.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __HISI_DECODER_H__ +#define __HISI_DECODER_H__ + +#include +#include +#include "../../decoder.h" + +#define DECODER_PGTBL_PGPRT_MASK GENMASK_ULL(47, 12) +#define DECODER_DMA_PAGE_ADDR_OFFSET 12 + +#define PGTLB_CACHE_IR_NC 0b00 +#define PGTLB_CACHE_IR_WBRA 0b01 +#define PGTLB_CACHE_IR_WT 0b10 +#define PGTLB_CACHE_IR_WB 0b11 +#define PGTLB_CACHE_OR_NC 0b0000 +#define PGTLB_CACHE_OR_WBRA 0b0100 +#define PGTLB_CACHE_OR_WT 0b1000 +#define PGTLB_CACHE_OR_WB 0b1100 +#define PGTLB_CACHE_SH_NSH 0b000000 +#define PGTLB_CACHE_SH_OSH 0b100000 +#define PGTLB_CACHE_SH_ISH 0b110000 + +#define PGTLB_ATTR_DEFAULT (PGTLB_CACHE_IR_WBRA | \ + PGTLB_CACHE_OR_WBRA | \ + PGTLB_CACHE_SH_ISH) + +#define RGTLB_TO_PGTLB 8 +#define DECODER_PAGE_ENTRY_SIZE 64 +#define DECODER_PAGE_SIZE (1 << 12) +#define DECODER_PAGE_TABLE_SIZE (1 << 12) +#define PAGE_TABLE_PAGE_SIZE (DECODER_PAGE_ENTRY_SIZE * DECODER_PAGE_SIZE) +#define RANGE_TABLE_PAGE_SIZE (DECODER_PAGE_ENTRY_SIZE * \ + DECODER_PAGE_SIZE * \ + RGTLB_TO_PGTLB) + +int hi_init_decoder_queue(struct ub_decoder *decoder); +void hi_uninit_decoder_queue(struct ub_decoder *decoder); + +int hi_create_decoder_table(struct ub_decoder *decoder); +void hi_free_decoder_table(struct ub_decoder *decoder); + +int hi_decoder_map(struct ub_decoder *decoder, struct decoder_map_info *info); +int hi_decoder_unmap(struct ub_decoder *decoder, phys_addr_t addr, u64 size); + +int hi_decoder_cmd_request(struct ub_decoder *decoder, phys_addr_t addr, + u64 size, enum ub_cmd_op_type op); +void hi_decoder_event_deal(struct ub_decoder *decoder); + +#endif /* __HISI_DECODER_H__ */ diff --git a/drivers/ub/ubus/vendor/hisilicon/hisi-msg.h b/drivers/ub/ubus/vendor/hisilicon/hisi-msg.h new file mode 100644 index 0000000000000000000000000000000000000000..d1a68934d174b8fcd27c77003d80f479cb156e38 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/hisi-msg.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __HISI_MSG_H__ +#define __HISI_MSG_H__ + +#include +#include +#include + +#include "../../msg.h" + +extern int msg_wait; + +/* Hardware register context */ +#define HI_MSGQ_SIZE 0x120 +#define SQ_ADDR_L 0x0 +#define SQ_ADDR_H 0x4 +#define SQ_PI 0x8 +#define SQ_CI 0xc +#define SQ_DEPTH 0x10 +#define SQ_STATUS 0x14 +#define SQ_INT_MSK 0x18 +#define RQ_ADDR_L 0x40 +#define RQ_ADDR_H 0x44 +#define RQ_PI 0x48 +#define RQ_CI 0x4c +#define RQ_DEPTH 0x50 +#define RQ_ENTRY_SIZE 0x54 +#define RQ_STATUS 0x58 +#define CQ_ADDR_L 0x70 +#define CQ_ADDR_H 0x74 +#define CQ_PI 0x78 +#define CQ_CI 0x7c +#define CQ_DEPTH 0x80 +#define CQ_STATUS 0x84 +#define CQ_INT_MASK 0x88 +#define CQ_INT_STATUS 0x8c +#define CQ_INT_RO 0x90 +#define MSGQ_RST 0xB0 +#define MSGQ_INT_SEL 0xC0 + +#define HI_MSG_RQE_SIZE 0x800 /* 2K */ +#define HI_MSG_SQE_PLD_SIZE 0x800 /* 2K */ +#define HI_SQ_CFG_DEPTH 0x400 +#define HI_RQ_CFG_DEPTH 0x400 +#define HI_CQ_CFG_DEPTH 0x400 +#define HI_MSG_SQE_SIZE 16 +#define HI_MSG_CQE_SIZE 16 + +enum hi_task_type { + PROTOCOL_MSG = 0, + PROTOCOL_ENUM = 1, + HISI_PRIVATE = 2, + TASK_TYPE_NUM +}; + +enum hi_msgq_private_opcode { + EU_TABLE_CFG_CMD = 2, + GET_UBMEM_EVENT_CMD = 4 +}; + +enum hi_msgq_user { + MSGQ_USER_BUS_DRV = 0, + MSGQ_USER_NUMS +}; + +enum hi_msgq_idx { + MSG_SQ = 0, + MSG_RQ = 1, + MSG_CQ = 2, + MSGQ_NUM +}; + +struct hi_msg_sqe { + /* DW0 */ + u32 task_type : 2; + u32 rsvd0 : 2; + u32 local : 1; + u32 dev_type : 2; + u32 icrc : 1; + union { + struct { + u8 type : 1; + u8 msg_code : 3; + u8 sub_msg_code : 4; + }; + u8 opcode; + }; + u32 p_len : 12; + u32 rsvd1 : 4; + /* DW1 */ + u32 msn : 16; + u32 rsvd3 : 16; + /* DW2 */ + u32 p_addr; + /* DW3 */ + u32 rsvd2; +}; + +struct hi_msg_cqe { + /* DW0 */ + u32 task_type : 2; + u32 rsvd0 : 6; + union { + struct { + u8 type : 1; + u8 msg_code : 3; + u8 sub_msg_code : 4; + }; + u8 opcode; + }; + u32 p_len : 12; + u32 rsvd1 : 4; + /* DW1 */ + u32 msn : 16; + u32 rsvd5 : 16; + /* DW2 */ + u32 rq_pi : 10; + u32 rsvd2 : 6; + u32 status : 8; + u32 rsvd3 : 8; + /* DW3 */ + u32 rsvd4; +}; + +struct hi_msg_queue { + union { + struct hi_msg_sqe *sqe; + void *rqe; + struct hi_msg_cqe *cqe; + void *entry; + }; + + dma_addr_t dma_addr; + + u16 ci; + u16 pi; + u16 depth; + u16 entry_size; + size_t total_size; + + spinlock_t lock; +}; + +#define HI_MSG_INT_NAME_LEN 32 +struct hi_msg_core { + struct device *dev; /* ubc->dev */ + phys_addr_t q_addr; /* MSGQ ctx's phy addr & size */ + size_t q_size; + void __iomem *reg_base; /* MSGQ context virtual address */ + + u32 virq; /* if virq == 0, then use poll mode, else int mode */ + u32 intx; + void (*irq_handler)(struct hi_msg_core *hmc); + void (*isr_handler)(struct hi_msg_core *hmc); + char queue_name[HI_MSG_INT_NAME_LEN]; + atomic_t cq_int_mask; + int user; + struct hi_msg_queue queue[MSGQ_NUM]; +}; + +#define q_used_cnt(q) (((q)->pi + (q)->depth - (q)->ci) % (q)->depth) +#define q_ptr_idx(q, p, i) (((q)->p + (i)) % (q)->depth) +#define cq_entry(hmc, idx) (&((hmc)->queue[MSG_CQ].cqe[idx])) +#define rq_entry(hmc, idx) \ + ((hmc)->queue[MSG_RQ].rqe + (HI_MSG_RQE_SIZE * (idx))) + +int hi_msg_cq_poll(struct hi_msg_core *hmc, int task_type, u16 msn); +void hi_msg_rq_update(struct hi_msg_core *hmc, int cq_idx); +void hi_msg_sqe_init(struct hi_msg_sqe *sqe, int msn, struct msg_info *info, + int task_type, u8 code); +void hi_msg_set_pkt_msn(struct msg_info *info, int task_type, u16 msn, + u8 msgq_id); +int hi_message_cqe_check(struct device *dev, struct hi_msg_sqe *sqe, + struct hi_msg_cqe *cqe, u16 rsp_pkt_size); +void hi_msg_rqe_get(struct hi_msg_core *hmc, void *buf, struct hi_msg_cqe *cqe); +u32 hi_msg_reg_read(struct hi_msg_core *hmc, u16 offset); +void hi_msg_reg_write(struct hi_msg_core *hmc, u16 offset, u32 val); +int hi_msg_core_init(struct hi_msg_core *hmc, int user); +void hi_msg_core_uninit(struct hi_msg_core *hmc); +int hi_msg_device_probe(struct ub_bus_controller *ubc); +void hi_msg_device_remove(struct ub_bus_controller *ubc); +int hi_message_sync_request(struct message_device *mdev, + struct msg_info *info, u8 opcode); +int hi_message_sync_request_sched(struct message_device *mdev, + struct msg_info *info, u8 opcode); +int hi_message_private(struct message_device *mdev, struct msg_info *info, + u8 opcode); +void hi_msg_debugfs_init(struct hi_msg_core *hmc); +void hi_msg_debugfs_uninit(struct hi_msg_core *hmc); +void ub_msg_dump_sq(struct hi_msg_sqe *sqe, void *sq_pld); +void ub_msg_dump_cq(struct hi_msg_cqe *cqe, void *rqe); + +#endif /* __HISI_MSG_H__ */ diff --git a/drivers/ub/ubus/vendor/hisilicon/hisi-ubus.c b/drivers/ub/ubus/vendor/hisilicon/hisi-ubus.c new file mode 100644 index 0000000000000000000000000000000000000000..3627b0e8f0185df7482217fe1df976f55a23eddd --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/hisi-ubus.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include + +#include "../../ubus.h" +#include "local-ras.h" +#include "hisi-ubus.h" + +int msg_wait = 1024; +module_param(msg_wait, int, 0444); +MODULE_PARM_DESC(msg_wait, "message wait timeout, default: 1024ms"); + +#define HISI_VENDOR_ID 0xCC08 +#define HISI_UBUS_DRV_NAME "hisi_ubus" + +static const struct ub_manage_subsystem_ops hisi_ub_manage_subsystem_ops = { + .vendor = HISI_VENDOR_ID, + .controller_probe = ub_bus_controller_probe, + .controller_remove = ub_bus_controller_remove, + .ras_handler_probe = ub_ras_handler_probe, + .ras_handler_remove = ub_ras_handler_remove +}; + +static const struct of_device_id hisi_ubus_of_match[] = { + { .compatible = "hisi,ubus", }, + {} +}; +MODULE_DEVICE_TABLE(of, hisi_ubus_of_match); + +static const struct acpi_device_id hisi_ubus_acpi_match[] = { + { "HISI0581", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, hisi_ubus_acpi_match); + +static struct platform_driver hisi_ubus_driver = { + .driver = { + .name = HISI_UBUS_DRV_NAME, + .of_match_table = hisi_ubus_of_match, + .acpi_match_table = hisi_ubus_acpi_match, + }, +}; + +static int __init hisi_ubus_driver_register(struct platform_driver *drv) +{ + int ret; + + ret = register_ub_manage_subsystem_ops(&hisi_ub_manage_subsystem_ops); + if (ret) + return ret; + + ret = platform_driver_register(drv); + if (ret) + goto platform_driver_register_fail; + + return 0; + +platform_driver_register_fail: + unregister_ub_manage_subsystem_ops(&hisi_ub_manage_subsystem_ops); + return ret; +} + +static void __exit hisi_ubus_driver_unregister(struct platform_driver *drv) +{ + platform_driver_unregister(drv); + unregister_ub_manage_subsystem_ops(&hisi_ub_manage_subsystem_ops); +} + +module_driver(hisi_ubus_driver, hisi_ubus_driver_register, hisi_ubus_driver_unregister); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Hisilicon UnifiedBus Manage Subsystem"); +MODULE_IMPORT_NS(UB_UBUS); diff --git a/drivers/ub/ubus/vendor/hisilicon/hisi-ubus.h b/drivers/ub/ubus/vendor/hisilicon/hisi-ubus.h new file mode 100644 index 0000000000000000000000000000000000000000..44e5fc8165babe9c5431738272c239e3fc7aaf51 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/hisi-ubus.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __HISI_UBUS_H__ +#define __HISI_UBUS_H__ + +#include + +#define MEM_INFO_NUM 5 +#define MB_SIZE_OFFSET 20 +#define HI_UBC_PRIVATE_DATA_RESERVED 3 +#define HI_UBC_PRIVATE_DATA_RESERVED2 111 +#define UB_MEM_VERSION_INVALID 0xffffffff +#define UB_MEM_VERSION_0 0 +#define UB_MEM_VERSION_1 1 +#define UB_MEM_VERSION_2 2 + +struct hi_mem_pa_info { + u64 decode_addr; + u32 cc_base_addr; + u32 cc_base_size; + u32 nc_base_addr; + u32 nc_base_size; +}; + +struct hi_ubc_private_data { + u32 ub_mem_version; + u8 max_addr_bits; + u8 reserved[HI_UBC_PRIVATE_DATA_RESERVED]; + struct hi_mem_pa_info mem_pa_info[MEM_INFO_NUM]; + u64 io_decoder_cmdq; + u64 io_decoder_evtq; + u8 features; + u8 reserved2[HI_UBC_PRIVATE_DATA_RESERVED2]; +}; + +int hi_eu_table_init(struct ub_bus_controller *ubc); +void hi_eu_table_uninit(struct ub_bus_controller *ubc); +int hi_eu_cfg(struct ub_bus_controller *ubc, bool add, u32 eid, u16 upi); +int hi_mem_decoder_create(struct ub_bus_controller *ubc); +void hi_mem_decoder_remove(struct ub_bus_controller *ubc); +void hi_register_ubmem_irq(struct ub_bus_controller *ubc); +void hi_unregister_ubmem_irq(struct ub_bus_controller *ubc); +int hi_send_entity_enable_msg(struct ub_entity *uent, u8 enable); +int hi_send_port_reset_msg(struct ub_entity *uent, u16 port_idx); +int ub_bus_controller_probe(struct ub_bus_controller *ubc); +void ub_bus_controller_remove(struct ub_bus_controller *ubc); + +#endif /* __HISI_UBUS_H__ */ diff --git a/drivers/ub/ubus/vendor/hisilicon/local-ras.c b/drivers/ub/ubus/vendor/hisilicon/local-ras.c new file mode 100644 index 0000000000000000000000000000000000000000..58e92d846c482b29df92d05bbd5c8bcf0a4ccd2e --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/local-ras.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi local_ras: " fmt + +#include +#include "../../ubus.h" +#include "../../ubus_driver.h" +#include "../../reset.h" +#include "../../port.h" +#include "local-ras.h" +#include "hisi-ubus.h" + +struct sub_module_info { + u32 sub_module_id; + const char *sub_module_name; +}; + +static struct sub_module_info hisi_ubus_sub_module[] = { + {0x1, "MISC.MISC_SLV"}, + {0x2, "MISC.IMP"}, + {0x3, "BA"}, + {0x4, "NL PORT"}, + {0x5, "NL DEVICE"}, + {0x6, "DLMAC"}, + {0x7, "MUXPCS"}, + {0x1D, "ETH"}, + {0x20, "TP_DAM"}, + {0x21, "TP_EUM"}, + {0x22, "TP_LRB"}, + {0x23, "TP_PPP"}, + {0x24, "TP_RQM"}, + {0x25, "TP_RXDMA_HEAD"}, + {0x26, "TP_RXP"}, + {0x27, "TP_SCC"}, + {0x28, "TP_TAI"}, + {0x29, "TP_TIMER"}, + {0x2a, "TP_TPCM"}, + {0x2b, "TP_TPGCM"}, + {0x2c, "TP_TPMM"}, + {0x2d, "TP_TPP"}, + {0x2e, "TP_TQEM"}, + {0x2f, "TP_TQS"}, + {0x30, "TP_UBOMMU"}, + {0x40, "TA_CQM"}, + {0x41, "TA_MRD"}, + {0x42, "TA_RSP"}, + {0x43, "TA_TM"}, + {0x44, "TA_TOM"}, + {0x45, "TA_TQC"}, + {0x46, "TA_TQEB"}, + {0x47, "TA_TQMS"}, + {0x48, "TA_USI"}, + {0x60, "ETH.CORE_TXMAC"}, + {0x61, "ETH.CORE_TDM"}, + {0x62, "ETH.CORE_TXPCS"}, + {0x63, "ETH.CORE_TXRSFEC"}, + {0x64, "ETH.CORE_MIB"}, + {0x65, "ETH.CORE_RXMAC"}, + {0x66, "ETH.CORE_RXPCS"}, + {0x67, "ETH.CORE_RXRSFEC"}, + {0x68, "ETH.CORE_RXPMACORE"}, + {0x69, "ETH.CORE_TXPMAL0"}, + {0x6b, "ETH.CORE_TXBRFEC"}, + {0x6c, "ETH.CORE_RXBRFEC"}, +}; + +static const char * const hisi_ubus_error_sev[] = { + [HISI_UBUS_ERR_SEV_RECOVERABLE] = "recoverable", + [HISI_UBUS_ERR_SEV_FATAL] = "fatal", + [HISI_UBUS_ERR_SEV_CORRECTED] = "corrected", + [HISI_UBUS_ERR_SEV_NONE] = "none", +}; + +struct ras_err_info { + u32 val_bit; + const char *err_msg; + u8 val; +}; + +static guid_t hisi_ubus_sec_guid = + GUID_INIT(0xC8B328A8, 0x9917, 0x4AF6, 0x9A, 0x13, 0x2E, + 0x08, 0xAB, 0x2E, 0x75, 0x86); + +static inline const char *hisi_ubus_get_string( + const char * const *array, size_t n, u32 id) +{ + return id < n && array[id] ? array[id] : "Unknown"; +} + +static const char *get_sub_module_name(u32 id) +{ + u8 i; + + for (i = 0; i < ARRAY_SIZE(hisi_ubus_sub_module); i++) { + if (hisi_ubus_sub_module[i].sub_module_id == id) + return hisi_ubus_sub_module[i].sub_module_name; + } + + return "Unknown"; +} + +#define HIP12_VERSION 8 +#define HIP12_SOC_ID 1 +#define ASCEND950_VERSION 2 +#define ASCEND950_SOC_ID 0x13 +static inline struct ub_bus_controller *find_bus_controller_by_errdata( + const struct hisi_ubus_error_data *edata) +{ + u32 ctl_no; + + /* only support on HIP12 and ASCEND950. */ + if (edata->version == HIP12_VERSION && edata->soc_id == HIP12_SOC_ID) + ctl_no = (u32)edata->socket_id; + else if (edata->version == ASCEND950_VERSION && edata->soc_id == ASCEND950_SOC_ID) + ctl_no = (u32)edata->socket_id; + else + return NULL; + + return ub_find_bus_controller(ctl_no); +} + +static void hisi_ubus_ras_print(struct ub_entity *uent, + const struct hisi_ubus_error_data *edata) +{ + struct ras_err_info ras_err_info[] = { + {HISI_UBUS_LOCAL_VALID_SOC_ID, + "SOC ID = %u\n", edata->soc_id}, + {HISI_UBUS_LOCAL_VALID_SOCKET_ID, + "Socket ID = %u\n", edata->socket_id}, + {HISI_UBUS_LOCAL_VALID_NIMBUS_ID, + "Nimbus ID = %u\n", edata->nimbus_id}, + {HISI_UBUS_LOCAL_VALID_CORE_ID, + "Core ID = core%u\n", edata->core_id}, + {HISI_UBUS_LOCAL_VALID_PORT_ID, + "Port ID = port%u\n", edata->port_id}, + }; + u8 i; + + ub_info(uent, "HISI : Ubus local RAS error\n"); + ub_info(uent, "Table version = %u\n", edata->version); + for (i = 0; i < ARRAY_SIZE(ras_err_info); i++) + if (edata->val_bits & ras_err_info[i].val_bit) + ub_info(uent, ras_err_info[i].err_msg, + ras_err_info[i].val); + + if (edata->val_bits & HISI_UBUS_LOCAL_VALID_SUB_MODULE_ID) + ub_info(uent, "Sub Module = %s\n", + get_sub_module_name((u32)edata->sub_module_id)); + + if (edata->val_bits & HISI_UBUS_LOCAL_VALID_ERR_SEVERITY) + ub_info(uent, "Error severity = %s\n", + hisi_ubus_get_string(hisi_ubus_error_sev, + ARRAY_SIZE(hisi_ubus_error_sev), + (u32)edata->err_severity)); + + if (edata->val_bits & HISI_UBUS_LOCAL_VALID_ERR_TYPE) + ub_info(uent, "Error type = %#x\n", edata->err_type); +} + +#define NL_PORT_MODULE_ID 0x4 +static inline bool is_nl_local_ras(u8 sub_module_id) +{ + return sub_module_id == NL_PORT_MODULE_ID; +} + +#define LQC_MOUDULE_ERR_BIT 7 +#define LQC_MOUDULE_ERR_MISC 1 +static inline bool is_nl_ssu_link_credi_overtime_err(const struct hisi_ubus_error_data *edata) +{ + if (DIV_ROUND_UP(edata->register_array_size, SZ_4) <= LQC_MOUDULE_ERR_MISC) + return false; + + return !!(edata->err_misc[LQC_MOUDULE_ERR_MISC] & (1U << LQC_MOUDULE_ERR_BIT)); +} + +static bool ubus_need_recover(const struct hisi_ubus_error_data *edata) +{ + if (edata->err_severity != HISI_UBUS_ERR_SEV_RECOVERABLE) + return false; + + if (!(edata->val_bits & HISI_UBUS_LOCAL_VALID_PORT_ID)) + return false; + + if (is_nl_local_ras(edata->sub_module_id)) + return is_nl_ssu_link_credi_overtime_err(edata); + + return true; +} + +static int ubus_port_recover(struct ub_entity *uent, u16 port_id) +{ + if (port_id < uent->port_nums && uent->ports[port_id].type == PHYSICAL) + return ub_port_reset(uent, port_id); + + ub_info(uent, "port[%u] no need reset by ubus.\n", port_id); + return 0; +} + +static int ubus_port_recover_cluster(struct ub_entity *uent, u16 port_id) +{ + struct ub_port *port; + int ret; + + if (port_id >= uent->port_nums || uent->ports[port_id].type != PHYSICAL) { + pr_err("port id is over port nums or port type is not physical\n"); + return -EINVAL; + } + + port = uent->ports + port_id; + ub_notify_share_port(port, UB_PORT_EVENT_RESET_PREPARE); + + ret = hi_send_port_reset_msg(uent, port_id); + if (ret) { + pr_err("ub vdm port reset failed, ret:%d\n", ret); + return ret; + } + + ub_notify_share_port(port, UB_PORT_EVENT_RESET_DONE); + + return 0; +} + +static int ubus_recover(struct ub_entity *uent, + const struct hisi_ubus_error_data *edata) +{ + int port_id; + + port_id = (int)edata->port_id; + if (uent->ubc->cluster) + return ubus_port_recover_cluster(uent, port_id); + else + return ubus_port_recover(uent, port_id); +} + +static void hisi_ubus_handle_error(struct ub_entity *uent, + const struct hisi_ubus_error_data *edata) +{ + int ret; + u32 i; + + hisi_ubus_ras_print(uent, edata); + ub_info(uent, "Reg Dump:\n"); +#define REGISTER_ARRAY_MAX_SIZE 256 + for (i = 0; i < DIV_ROUND_UP( + min(edata->register_array_size, REGISTER_ARRAY_MAX_SIZE), SZ_4); i++) + ub_info(uent, "ERR_MISC_%u = %#x\n", i, edata->err_misc[i]); + if (edata->register_array_size > REGISTER_ARRAY_MAX_SIZE) + ub_warn(uent, "register array size is exceed max array size %d, only parts of data were printed.\n", + REGISTER_ARRAY_MAX_SIZE); + + if (!ubus_need_recover(edata)) { + ub_info(uent, "ubus no need recover.\n"); + return; + } + + ret = ubus_recover(uent, edata); + if (ret) + ub_err(uent, "ubus recover failed, ret=%d\n", ret); +} + +static bool ubus_error_supported(const struct hisi_ubus_error_data *error_data) +{ +#define UNION_DIE 1 +#define HIP12_UB_MODULE_ID 0x2d +#define ASCEND950_UB_MODULE_ID 0x31 + if (!(HISI_UBUS_LOCAL_VALID_MODULE_ID & error_data->val_bits) || + !(HISI_UBUS_LOCAL_VALID_NIMBUS_ID & error_data->val_bits) || + !(HISI_UBUS_LOCAL_VALID_SOC_ID & error_data->soc_id)) + return false; + + if (error_data->soc_id == HIP12_SOC_ID) + return error_data->nimbus_id == UNION_DIE && + error_data->module_id == HIP12_UB_MODULE_ID; + + if (error_data->soc_id == ASCEND950_SOC_ID) + return error_data->module_id == ASCEND950_UB_MODULE_ID; + + return false; +} + +static int hisi_ubus_notify_error(struct notifier_block *nb, unsigned long event, void *data) +{ + const struct hisi_ubus_error_data *error_data; + struct acpi_hest_generic_data *gdata; + struct ub_bus_controller *ubc; + guid_t err_sec_guid; + + gdata = (struct acpi_hest_generic_data *)data; + import_guid(&err_sec_guid, gdata->section_type); + if (!guid_equal(&err_sec_guid, &hisi_ubus_sec_guid)) + return NOTIFY_DONE; + + error_data = (struct hisi_ubus_error_data *)acpi_hest_get_payload(gdata); + if (!ubus_error_supported(error_data)) + return NOTIFY_DONE; + + ubc = find_bus_controller_by_errdata(error_data); + if (!ubc) + return NOTIFY_DONE; + + hisi_ubus_handle_error(ubc->uent, error_data); + return NOTIFY_OK; +} + +static struct hisi_ubus_error_private *hisi_ubus_error_private_p; + +void ub_ras_handler_remove(void) +{ + if (hisi_ubus_error_private_p) { + ghes_unregister_vendor_record_notifier( + &hisi_ubus_error_private_p->nb); + kfree(hisi_ubus_error_private_p); + hisi_ubus_error_private_p = NULL; + } +} + +int ub_ras_handler_probe(void) +{ + int ret; + + hisi_ubus_error_private_p = kzalloc(sizeof(struct hisi_ubus_error_private), + GFP_KERNEL); + if (!hisi_ubus_error_private_p) + return -ENOMEM; + + hisi_ubus_error_private_p->nb.notifier_call = hisi_ubus_notify_error; + ret = ghes_register_vendor_record_notifier( + &hisi_ubus_error_private_p->nb); + if (ret) { + pr_err("register ubus error handler with apei failed\n"); + kfree(hisi_ubus_error_private_p); + hisi_ubus_error_private_p = NULL; + } + + return ret; +} diff --git a/drivers/ub/ubus/vendor/hisilicon/local-ras.h b/drivers/ub/ubus/vendor/hisilicon/local-ras.h new file mode 100644 index 0000000000000000000000000000000000000000..b01712efb514df1f2f285cfbae79cb858c7fba50 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/local-ras.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __LOCAL_RAS_H__ +#define __LOCAL_RAS_H__ + +#define HISI_UBUS_ERR_MISC_REGS 64 + +#define HISI_UBUS_LOCAL_VALID_SOC_ID BIT(0) +#define HISI_UBUS_LOCAL_VALID_SOCKET_ID BIT(1) +#define HISI_UBUS_LOCAL_VALID_TOTEM_ID BIT(2) +#define HISI_UBUS_LOCAL_VALID_NIMBUS_ID BIT(3) +#define HISI_UBUS_LOCAL_VALID_SUB_SYSTEM_ID BIT(4) +#define HISI_UBUS_LOCAL_VALID_MODULE_ID BIT(5) +#define HISI_UBUS_LOCAL_VALID_SUB_MODULE_ID BIT(6) +#define HISI_UBUS_LOCAL_VALID_CORE_ID BIT(7) +#define HISI_UBUS_LOCAL_VALID_PORT_ID BIT(8) +#define HISI_UBUS_LOCAL_VALID_ERR_TYPE BIT(9) +#define HISI_UBUS_LOCAL_VALID_ERR_INFO BIT(10) +#define HISI_UBUS_LOCAL_VALID_ERR_SEVERITY BIT(11) +#define HISI_UBUS_LOCAL_VALID_REG_SIZE BIT(12) + +struct hisi_ubus_error_data { + u32 val_bits; + u8 version; + u8 soc_id; + u8 socket_id; + u8 totem_id; + u8 nimbus_id; + u8 sub_system_id; + u8 module_id; + u8 sub_module_id; + u8 core_id; + u8 port_id; + u16 err_type; + u64 rsvd; + u8 err_severity; + u8 reserv[3]; + u32 register_array_size; + u32 err_misc[]; +}; + +struct hisi_ubus_error_private { + struct notifier_block nb; +}; + +enum hisi_ubus_err_severity { + HISI_UBUS_ERR_SEV_RECOVERABLE, + HISI_UBUS_ERR_SEV_FATAL, + HISI_UBUS_ERR_SEV_CORRECTED, + HISI_UBUS_ERR_SEV_NONE, +}; + +void ub_ras_handler_remove(void); +int ub_ras_handler_probe(void); + +#endif /* __LOCAL_RAS_H__ */ diff --git a/drivers/ub/ubus/vendor/hisilicon/memory.c b/drivers/ub/ubus/vendor/hisilicon/memory.c new file mode 100644 index 0000000000000000000000000000000000000000..e936d9ae79a4f33f3de1907a5a1aa79d82531e67 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/memory.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi memory: " fmt + +#include +#include + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../memory.h" +#include "hisi-msg.h" +#include "hisi-ubus.h" +#define CREATE_TRACE_POINTS +#include "memory_trace.h" + +#define DRAIN_ENABLE_REG_OFFSET 0x24 +#define DRAIN_STATE_REG_OFFSET 0x28 + +#define HI_GET_UBMEM_EVENT_REQ_SIZE 4 +#define HI_GET_UBMEM_EVENT_RSP_SIZE 772 +#define MEM_EVENT_MAX_NUM 16 +#define MAR_ERR_ADDR_COUNT 10 +#define MAR_ERR_ADDR_SIZE 2 +#define MEM_DECODER_NUMBER_V1 5 +#define MEM_DECODER_NUMBER_V2 2 + +#define hpa_gen(addr_h, addr_l) (((u64)(addr_h) << 32) | (addr_l)) + +#define UB_MEM_PORT_WARNING 18 +#define UB_MEM_PORT_RECOVERY 19 + +static u8 ub_mem_num; + +struct ub_mem_decoder { + struct device *dev; + struct ub_entity *uent; + void *base_reg; +}; + +struct hi_ubmem_event { + u32 device_ras_status3; + u32 device_ras_status4; + u32 err_addr[MAR_ERR_ADDR_COUNT]; +}; + +struct hi_get_ubmem_event_rsp { + u32 event_num; + struct hi_ubmem_event event_info[MEM_EVENT_MAX_NUM]; +}; + +struct hi_get_ubmem_event_req { + u32 rsv0; +}; + +struct hi_get_ubmem_event_pld { + union { + struct hi_get_ubmem_event_req req; + struct hi_get_ubmem_event_rsp rsp; + }; +}; + +static bool hi_mem_validate_pa(struct ub_bus_controller *ubc, + u64 pa_start, u64 pa_end, bool cacheable); + +static void hi_mem_drain_start(struct ub_bus_controller *ubc) +{ + struct ub_mem_decoder *decoder, *data = ubc->mem_device->priv_data; + + if (!data) { + dev_err(&ubc->dev, "ubc mem_decoder is null.\n"); + return; + } + + for (int i = 0; i < ub_mem_num; i++) { + decoder = &data[i]; + writel(0, decoder->base_reg + DRAIN_ENABLE_REG_OFFSET); + writel(1, decoder->base_reg + DRAIN_ENABLE_REG_OFFSET); + } +} + +static int hi_mem_drain_state(struct ub_bus_controller *ubc) +{ + struct ub_mem_decoder *decoder, *data = ubc->mem_device->priv_data; + struct ub_mem_device *mem_device = ubc->mem_device; + int val = 0; + + if (!data) { + dev_err(mem_device->dev, "ubc mem_decoder is null.\n"); + return 0; + } + + for (int i = 0; i < ub_mem_num; i++) { + decoder = &data[i]; + val = readb(decoder->base_reg + DRAIN_STATE_REG_OFFSET) & 0x1; + dev_info_ratelimited(decoder->dev, "ub memory decoder[%d] drain state, val=%d\n", + i, val); + if (!val) + return val; + } + + return val; +} + +static const struct ub_mem_device_ops device_ops = { + .mem_drain_start = hi_mem_drain_start, + .mem_drain_state = hi_mem_drain_state, + .mem_validate_pa = hi_mem_validate_pa, +}; + +static int save_ras_err_info(struct ub_mem_device *mem_device, + enum ras_err_type type, u64 val0, u64 val1) +{ + struct ub_mem_ras_err_info err_info = { + .type = type, + .val0 = val0, + .val1 = val1, + }; + + if (!kfifo_put(&mem_device->ras_ctx.ras_fifo, err_info)) { + dev_err(mem_device->dev, "kfifo put failed!\n"); + return -ENOMEM; + } + + return 0; +} + +static irqreturn_t hi_mem_ras_isr(int irq, void *context) +{ + struct ub_bus_controller *ubc = (struct ub_bus_controller *)context; + struct ub_mem_ras_ctx *ras_ctx = &ubc->mem_device->ras_ctx; + struct ub_mem_ras_err_info err_info; + ubmem_ras_handler handler; + u64 ctl_no = ubc->ctl_no; + u64 vals[3]; + int ret; + + handler = ub_mem_ras_handler_get(); + while (kfifo_get(&ras_ctx->ras_fifo, &err_info)) { + trace_mem_ras_event(ubc->mem_device, &err_info); + pr_info("ras: type=%u\n", err_info.type); + if (err_info.type == UB_MEM_PORT_WARNING || + err_info.type == UB_MEM_PORT_RECOVERY) { + vals[0] = err_info.val0; + vals[1] = err_info.val1; + vals[2] = ctl_no; + if (handler) { + ret = handler((u64)vals, err_info.type); + WARN_ON(ret); + } + } else { + if (handler) { + ret = handler(err_info.val0, err_info.type); + WARN_ON(ret); + } + } + } + + return IRQ_HANDLED; +} + +static int err_type_bitmap[] = { + /* DEVICE_RAS_STATUS_3 */ + [UB_MEM_ATOMIC_DATA_ERR] = 31, + [UB_MEM_READ_DATA_ERR] = 28, + [UB_MEM_FLOW_POISON] = 27, + [UB_MEM_FLOW_READ_AUTH_POISON] = 23, + [UB_MEM_FLOW_READ_AUTH_RESPERR] = 22, + [UB_MEM_TIMEOUT_POISON] = 21, + [UB_MEM_TIMEOUT_RESPERR] = 20, + [UB_MEM_READ_DATA_POISON] = 19, + [UB_MEM_READ_DATA_RESPERR] = 18, + /* DEVICE_RAS_STATUS_4 */ + [MAR_NOPORT_VLD_INT_ERR] = 26, + [MAR_FLUX_INT_ERR] = 25, + [MAR_WITHOUT_CXT_ERR] = 24, + [RSP_BKPRE_OVER_TIMEOUT_ERR] = 10, + /* DEVICE_RAS_STATUS_4 need save addr */ + [MAR_NEAR_AUTH_FAIL_ERR] = 21, + [MAR_FAR_AUTH_FAIL_ERR] = 22, + [MAR_TIMEOUT_ERR] = 23, + [MAR_ILLEGAL_ACCESS_ERR] = 9, + [REMOTE_READ_DATA_ERR_OR_WRITE_RESPONSE_ERR] = 11, + /* DEVICE_RAS_STATUS_4 need save transaction id and port bitmap */ + [UB_MEM_PORT_WARNING] = 31, + /* DEVICE_RAS_STATUS_4 need save transaction id */ + [UB_MEM_PORT_RECOVERY] = 30, +}; + +static int save_injected_route_info(struct ub_bus_controller *ubc, + struct hi_ubmem_event *info, + unsigned long status4_bitmap) +{ + u32 addr_h, addr_l; + u64 val0, val1; + int ret, i; + + if (test_bit(err_type_bitmap[UB_MEM_PORT_WARNING], &status4_bitmap)) { + i = UB_MEM_PORT_WARNING; + addr_h = info->err_addr[1]; + addr_l = info->err_addr[0]; + val0 = hpa_gen(addr_h, addr_l); + addr_h = info->err_addr[3]; + addr_l = info->err_addr[2]; + val1 = hpa_gen(addr_h, addr_l); + ret = save_ras_err_info(ubc->mem_device, (enum ras_err_type)i, val0, val1); + if (ret) + return ret; + } + + if (test_bit(err_type_bitmap[UB_MEM_PORT_RECOVERY], &status4_bitmap)) { + i = UB_MEM_PORT_RECOVERY; + addr_h = info->err_addr[5]; + addr_l = info->err_addr[4]; + val0 = hpa_gen(addr_h, addr_l); + ret = save_ras_err_info(ubc->mem_device, (enum ras_err_type)i, val0, 0); + if (ret) + return ret; + } + + return 0; +} + +static int save_ras_err_info_all(struct ub_bus_controller *ubc, struct hi_ubmem_event *info) +{ + unsigned long status3_bitmap = (unsigned long)info->device_ras_status3; + unsigned long status4_bitmap = (unsigned long)info->device_ras_status4; + u32 addr_h, addr_l; + int ret = -EINVAL; + int index, i; + u64 val0 = 0; + + /* receives and processes one injected route event at a time */ + if (test_bit(err_type_bitmap[UB_MEM_PORT_WARNING], &status4_bitmap) || + test_bit(err_type_bitmap[UB_MEM_PORT_RECOVERY], &status4_bitmap)) + return save_injected_route_info(ubc, info, status4_bitmap); + + for (i = UB_MEM_ATOMIC_DATA_ERR; i <= UB_MEM_READ_DATA_RESPERR; i++) { + if (test_bit(err_type_bitmap[i], &status3_bitmap)) { + ret = save_ras_err_info(ubc->mem_device, (enum ras_err_type)i, val0, 0); + if (ret) + return ret; + } + } + + for (i = MAR_FLUX_INT_ERR; i <= RSP_BKPRE_OVER_TIMEOUT_ERR; i++) { + if (test_bit(err_type_bitmap[i], &status4_bitmap)) { + ret = save_ras_err_info(ubc->mem_device, (enum ras_err_type)i, val0, 0); + if (ret) + return ret; + } + } + + for (i = MAR_NEAR_AUTH_FAIL_ERR; i <= REMOTE_READ_DATA_ERR_OR_WRITE_RESPONSE_ERR; i++) { + if (test_bit(err_type_bitmap[i], &status4_bitmap)) { + index = MAR_ERR_ADDR_SIZE * (i - MAR_NEAR_AUTH_FAIL_ERR); + addr_h = info->err_addr[index + 1]; + addr_l = info->err_addr[index]; + val0 = hpa_gen(addr_h, addr_l); + ret = save_ras_err_info(ubc->mem_device, (enum ras_err_type)i, val0, 0); + if (ret) + return ret; + } + } + + /* if no_port_vld and near_auth_fail report at the same time, ignore no_port_vld */ + if (test_bit(err_type_bitmap[MAR_NOPORT_VLD_INT_ERR], &status4_bitmap) && + !test_bit(err_type_bitmap[MAR_NEAR_AUTH_FAIL_ERR], &status4_bitmap)) { + i = MAR_NOPORT_VLD_INT_ERR; + ret = save_ras_err_info(ubc->mem_device, (enum ras_err_type)i, val0, 0); + } + + return ret; +} + +static irqreturn_t hi_mem_ras_irq(int irq, void *context) +{ + struct ub_bus_controller *ubc = (struct ub_bus_controller *)context; + struct hi_get_ubmem_event_pld pld = {}; + struct msg_info info = {}; + u32 event_cnt; + int ret; + + message_info_init(&info, ubc->uent, &pld, &pld, + (HI_GET_UBMEM_EVENT_REQ_SIZE << MSG_REQ_SIZE_OFFSET) | + HI_GET_UBMEM_EVENT_RSP_SIZE); + ret = hi_message_private(ubc->mdev, &info, GET_UBMEM_EVENT_CMD); + if (ret) { + dev_err(&ubc->dev, "get ubmem event failed, ret=%d\n", + ret); + return IRQ_HANDLED; + } + + event_cnt = pld.rsp.event_num; + if (event_cnt == 0 || event_cnt > MEM_EVENT_MAX_NUM) { + dev_warn(&ubc->dev, "event_cnt [%u] is invalid\n", event_cnt); + return IRQ_HANDLED; + } + + for (u32 i = 0; i < event_cnt; i++) { + ret = save_ras_err_info_all(ubc, &pld.rsp.event_info[i]); + if (ret == -EINVAL) { + dev_err(&ubc->dev, "save_ras_err_info failed, ret=%d\n", ret); + return IRQ_HANDLED; + } + } + + return IRQ_WAKE_THREAD; +} + +static bool is_ub_mem_version_valid(struct ub_bus_controller *ubc) +{ + struct hi_ubc_private_data *data = ubc->data; + + if (!data || data->ub_mem_version == UB_MEM_VERSION_INVALID) + return false; + return true; +} + +static int hi_mem_decoder_create_one(struct ub_bus_controller *ubc, int index) +{ + struct ub_mem_decoder *decoder, *priv_data = ubc->mem_device->priv_data; + struct hi_ubc_private_data *data = ubc->data; + + decoder = &priv_data[index]; + decoder->dev = &ubc->dev; + decoder->uent = ubc->uent; + + decoder->base_reg = ioremap(data->mem_pa_info[index].decode_addr, + SZ_64); + if (!decoder->base_reg) { + dev_err(decoder->dev, "ub mem decoder base reg ioremap failed.\n"); + return -ENOMEM; + } + + return 0; +} + +static void hi_mem_decoder_remove_one(struct ub_bus_controller *ubc, int index) +{ + struct ub_mem_decoder *priv_data = ubc->mem_device->priv_data; + + iounmap(priv_data[index].base_reg); +} + +static u8 get_mem_decoder_number(struct hi_ubc_private_data *data) +{ + switch (data->ub_mem_version) { + case UB_MEM_VERSION_0: + case UB_MEM_VERSION_1: + return MEM_DECODER_NUMBER_V1; + case UB_MEM_VERSION_2: + return MEM_DECODER_NUMBER_V2; + default: + return 0; + } +} + +int hi_mem_decoder_create(struct ub_bus_controller *ubc) +{ + struct hi_ubc_private_data *data = ubc->data; + struct ub_mem_device *mem_device; + void *priv_data; + int ret; + + if (!is_ub_mem_version_valid(ubc)) { + dev_info(&ubc->dev, "Don't need to create mem decoder\n"); + return 0; + } + + ub_mem_num = get_mem_decoder_number(data); + if (!ub_mem_num) + return -EINVAL; + + mem_device = kzalloc(sizeof(*mem_device), GFP_KERNEL); + if (!mem_device) + return -ENOMEM; + + priv_data = kcalloc(ub_mem_num, sizeof(struct ub_mem_decoder), + GFP_KERNEL); + if (!priv_data) { + kfree(mem_device); + return -ENOMEM; + } + + mem_device->dev = &ubc->dev; + mem_device->uent = ubc->uent; + mem_device->ubmem_irq_num = -1; + mem_device->ops = &device_ops; + mem_device->priv_data = priv_data; + ubc->mem_device = mem_device; + + for (int i = 0; i < ub_mem_num; i++) { + ret = hi_mem_decoder_create_one(ubc, i); + if (ret) { + dev_err(&ubc->dev, "hi mem create decoder %d failed\n", i); + for (int j = i - 1; j >= 0; j--) + hi_mem_decoder_remove_one(ubc, j); + + kfree(mem_device->priv_data); + kfree(mem_device); + ubc->mem_device = NULL; + return ret; + } + } + + return ret; +} + +void hi_mem_decoder_remove(struct ub_bus_controller *ubc) +{ + if (!ubc->mem_device) + return; + + if (!is_ub_mem_version_valid(ubc)) { + dev_info(&ubc->dev, "Don't need to remove mem decoder\n"); + return; + } + + for (int i = 0; i < ub_mem_num; i++) + hi_mem_decoder_remove_one(ubc, i); + + kfree(ubc->mem_device->priv_data); + kfree(ubc->mem_device); + ubc->mem_device = NULL; +} + +void hi_register_ubmem_irq(struct ub_bus_controller *ubc) +{ + struct ub_entity *uent = ubc->uent; + int irq_num, ret; + u32 usi_idx; + + if (!ubc->mem_device) { + pr_err("register ubmem irq failed, mem device is NULL!\n"); + return; + } + + if (!is_ub_mem_version_valid(ubc)) { + dev_info(&ubc->dev, "Don't need to register_ubmem_irq\n"); + return; + } + + ret = ub_cfg_read_dword(uent, UB_MEM_USI_IDX, &usi_idx); + if (ret) { + ub_err(uent, "get ubmem usi idx failed, ret=%d\n", ret); + return; + } + + irq_num = ub_irq_vector(uent, usi_idx); + if (irq_num < 0) { + ub_err(uent, "ub get irq vector failed, irq num=%d\n", irq_num); + return; + } + + INIT_KFIFO(ubc->mem_device->ras_ctx.ras_fifo); + + ret = request_threaded_irq(irq_num, hi_mem_ras_irq, + hi_mem_ras_isr, IRQF_SHARED, + "ub_mem_event", ubc); + if (ret) { + ub_err(uent, "ubmem request_irq failed, ret=%d\n", ret); + return; + } + + ubc->mem_device->ubmem_irq_num = irq_num; +} + +void hi_unregister_ubmem_irq(struct ub_bus_controller *ubc) +{ + int irq_num; + + if (!ubc->mem_device) { + dev_err(&ubc->dev, "mem device is NULL!\n"); + return; + } + + if (!is_ub_mem_version_valid(ubc)) { + dev_info(&ubc->dev, "Don't need to unregister_ubmem_irq\n"); + return; + } + + irq_num = ubc->mem_device->ubmem_irq_num; + if (irq_num < 0) + return; + + free_irq((unsigned int)irq_num, (void *)ubc); +} + +#define MB_SIZE_OFFSET 20 + +static bool ub_hpa_valid(u64 pa_start, u64 pa_end, u32 base_addr, u32 size) +{ + if (pa_start >= ((u64)base_addr << MB_SIZE_OFFSET) && + pa_end < (((u64)base_addr + (u64)size) << MB_SIZE_OFFSET)) + return true; + + return false; +} + +static bool hi_mem_validate_pa(struct ub_bus_controller *ubc, + u64 pa_start, u64 pa_end, bool cacheable) +{ + struct hi_ubc_private_data *data; + + if (!ubc->data) { + dev_err(&ubc->dev, "Ubc data is null.\n"); + return false; + } + + if (pa_end < pa_start) { + dev_err(&ubc->dev, "pa_start is over pa_end.\n"); + return false; + } + + data = ubc->data; + for (u16 i = 0; i < ub_mem_num; i++) { + if (ub_hpa_valid(pa_start, pa_end, + data->mem_pa_info[i].cc_base_addr, + data->mem_pa_info[i].cc_base_size) && + cacheable) + return true; + + if (ub_hpa_valid(pa_start, pa_end, + data->mem_pa_info[i].nc_base_addr, + data->mem_pa_info[i].nc_base_size) && + !cacheable) + return true; + } + + return false; +} diff --git a/drivers/ub/ubus/vendor/hisilicon/memory_trace.h b/drivers/ub/ubus/vendor/hisilicon/memory_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..1de776fdd97b06e52b49a8baf2a0f5e4c4fb153e --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/memory_trace.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +/* This must be outside ifdef __HISI_MEMORY_TRACE_H__ */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ub_memory + +#if !defined(__HISI_MEMORY_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __HISI_MEMORY_TRACE_H__ + +#include + +TRACE_EVENT(mem_ras_event, + TP_PROTO(struct ub_mem_device *device, struct ub_mem_ras_err_info *info), + TP_ARGS(device, info), + + TP_STRUCT__entry( + __field(u32, eid) + __field(u32, cna) + __field(u8, type) + __field(u64, val0) + __field(u64, val1) + ), + + TP_fast_assign( + __entry->eid = device->uent->eid; + __entry->cna = device->uent->cna; + __entry->type = (u8)info->type; + __entry->val0 = info->val0; + __entry->val1 = info->val1; + ), + + TP_printk( + "%u-%u-%u-%llu-%llu", __entry->eid, __entry->cna, + __entry->type, __entry->val0, __entry->val1 + ) +); + +#endif /* __HISI_MEMORY_TRACE_H__ */ + +/* This must be outside ifdef __HISI_MEMORY_TRACE_H__ */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/ub/ubus/vendor/hisilicon +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE memory_trace +#include diff --git a/drivers/ub/ubus/vendor/hisilicon/msg-core.c b/drivers/ub/ubus/vendor/hisilicon/msg-core.c new file mode 100644 index 0000000000000000000000000000000000000000..36b7b5cfa56098c316c28420e8e73a172af84fc1 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/msg-core.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#include +#include + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../enum.h" +#include "hisi-msg.h" + +#define HI_MSG_RQE_SIZE_128 0x80 +#define hi_msg_rqe_size_hw(sz_sw) (ilog2((sz_sw) / (HI_MSG_RQE_SIZE_128))) + +enum hi_sqe_ent_type { TYPE_BUS_CONTROLLER = 0, TYPE_IDEV = 1 }; +enum hi_cqe_status { CQE_SUCCESS, CQE_FAIL }; + +struct hi_msgq_reg { + u32 pi; + u32 ci; + u32 depth; + u32 entry_size; + u32 addr_l; + u32 addr_h; +}; + +static struct hi_msgq_reg msgq_reg[MSGQ_NUM] = { + { SQ_PI, SQ_CI, SQ_DEPTH, 0, SQ_ADDR_L, SQ_ADDR_H }, + { RQ_PI, RQ_CI, RQ_DEPTH, RQ_ENTRY_SIZE, RQ_ADDR_L, RQ_ADDR_H }, + { CQ_PI, CQ_CI, CQ_DEPTH, 0, CQ_ADDR_L, CQ_ADDR_H } +}; + +static const char * const msgq_user_name[] = { "UBUS" }; + +struct hi_msgq_cfg { + u32 depth; + u32 entry_size; +}; + +static int hi_msg_queue_sw_init(struct hi_msg_core *hmc, int user, + int idx) +{ + struct hi_msgq_cfg msgq_cfg[MSGQ_USER_NUMS][MSGQ_NUM] = { + { { HI_SQ_CFG_DEPTH, HI_MSG_SQE_SIZE }, + { HI_RQ_CFG_DEPTH, HI_MSG_RQE_SIZE }, + { HI_CQ_CFG_DEPTH, HI_MSG_CQE_SIZE } } + }; + struct hi_msg_queue *q = &hmc->queue[idx]; + struct device *dev = hmc->dev; + + q->ci = 0; + q->pi = 0; + q->depth = msgq_cfg[user][idx].depth; + q->entry_size = msgq_cfg[user][idx].entry_size; + q->total_size = q->depth * q->entry_size; + spin_lock_init(&q->lock); + + if (idx == MSG_SQ) + q->total_size += HI_MSG_SQE_PLD_SIZE * q->depth; + + q->entry = dma_alloc_coherent(dev, q->total_size, &q->dma_addr, + GFP_KERNEL); + if (!q->entry) + return -ENOMEM; + + dev_info(dev, "%s queue=%d, ci=%#x, pi=%#x, depth=%#x, entry_size=%#x, size=%#lx\n", + msgq_user_name[user], idx, q->ci, q->pi, q->depth, + q->entry_size, q->total_size); + + return 0; +} + +static void hi_msg_queue_sw_uninit(struct hi_msg_core *hmc, int idx) +{ + struct hi_msg_queue *q = &hmc->queue[idx]; + + dma_free_coherent(hmc->dev, q->total_size, q->entry, q->dma_addr); +} + +u32 hi_msg_reg_read(struct hi_msg_core *hmc, u16 offset) +{ + return readl(hmc->reg_base + offset); +} + +void hi_msg_reg_write(struct hi_msg_core *hmc, u16 offset, u32 val) +{ + writel(val, hmc->reg_base + offset); +} + +static void hi_msg_queue_hw_init(struct hi_msg_core *hmc, int idx) +{ + struct hi_msg_queue *q = &hmc->queue[idx]; + + /* cfg depth pre */ + hi_msg_reg_write(hmc, msgq_reg[idx].depth, q->depth); + + if (idx == MSG_SQ) + hi_msg_reg_write(hmc, msgq_reg[idx].pi, q->pi); + else + hi_msg_reg_write(hmc, msgq_reg[idx].ci, q->ci); + + hi_msg_reg_write(hmc, msgq_reg[idx].addr_l, q->dma_addr & 0xffffffff); + hi_msg_reg_write(hmc, msgq_reg[idx].addr_h, + q->dma_addr >> BITS_PER_TYPE(u32)); + + if (idx == MSG_RQ) + hi_msg_reg_write(hmc, msgq_reg[idx].entry_size, + hi_msg_rqe_size_hw(HI_MSG_RQE_SIZE)); +} + +static void hi_msg_reset_queue(struct hi_msg_core *hmc) +{ + hi_msg_reg_write(hmc, MSGQ_RST, 1); + hi_msg_reg_write(hmc, MSGQ_RST, 0); +} + +static irqreturn_t hi_msg_irq(int irq, void *context) +{ + struct hi_msg_core *hmc = (struct hi_msg_core *)context; + struct hi_msg_queue *cq = &hmc->queue[MSG_CQ]; + unsigned long flags; + u32 ro; + + ro = hi_msg_reg_read(hmc, CQ_INT_RO); + if (!ro) + return IRQ_NONE; + + hi_msg_reg_write(hmc, CQ_INT_MASK, 0x1); + + if (hmc->irq_handler) + hmc->irq_handler(hmc); + + if (hmc->isr_handler) + return IRQ_WAKE_THREAD; + + hi_msg_reg_write(hmc, CQ_INT_STATUS, 0x1); + + spin_lock_irqsave(&cq->lock, flags); + if (cq->pi == cq->ci) { + hi_msg_reg_write(hmc, CQ_INT_STATUS, 0x1); + hi_msg_reg_write(hmc, CQ_INT_MASK, 0x0); + } else { + atomic_set(&hmc->cq_int_mask, 1); + } + spin_unlock_irqrestore(&cq->lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t hi_msg_isr(int irq, void *context) +{ + struct hi_msg_core *hmc = (struct hi_msg_core *)context; + + hmc->isr_handler(hmc); + + hi_msg_reg_write(hmc, CQ_INT_STATUS, 0x1); + hi_msg_reg_write(hmc, CQ_INT_MASK, 0x0); + + return IRQ_HANDLED; +} + +static int hi_msg_queue_irq_init(struct hi_msg_core *hmc) +{ + int ret; + + /* Config sq int mask for imp */ + hi_msg_reg_write(hmc, SQ_INT_MSK, 0x0); + + if (!hmc->virq) + return 0; + + ret = request_threaded_irq(hmc->virq, hi_msg_irq, hi_msg_isr, + IRQF_ONESHOT | IRQF_SHARED, + hmc->queue_name, hmc); + if (ret) { + dev_err(hmc->dev, "request msgq irq failed\n"); + return ret; + } + dev_info(hmc->dev, "request irq[%u] succeeded\n", hmc->virq); + hi_msg_reg_write(hmc, MSGQ_INT_SEL, hmc->intx); + hi_msg_reg_write(hmc, CQ_INT_STATUS, 0x1); + hi_msg_reg_write(hmc, CQ_INT_MASK, 0x0); + + return 0; +} + +static void hi_msg_queue_irq_uninit(struct hi_msg_core *hmc) +{ + if (hmc->virq) { + hi_msg_reg_write(hmc, CQ_INT_MASK, 0x1); + hi_msg_reg_write(hmc, CQ_INT_STATUS, 0x1); + + free_irq(hmc->virq, hmc); + } + + hi_msg_reg_write(hmc, SQ_INT_MSK, 0x1); +} + +int hi_msg_core_init(struct hi_msg_core *hmc, int user) +{ + int i, j, ret; + + hmc->user = user; + hmc->reg_base = ioremap(hmc->q_addr, hmc->q_size); + if (!hmc->reg_base) + return -ENOMEM; + + for (i = 0; i < MSGQ_NUM; i++) { + ret = hi_msg_queue_sw_init(hmc, user, i); + if (ret) + goto sw_uninit; + } + + hi_msg_reset_queue(hmc); + + for (i = 0; i < MSGQ_NUM; i++) + hi_msg_queue_hw_init(hmc, i); + + ret = hi_msg_queue_irq_init(hmc); + if (ret) + goto sw_uninit; /* Now i = MSGQ_NUM */ + + hi_msg_debugfs_init(hmc); + + return 0; +sw_uninit: + hi_msg_reset_queue(hmc); + + for (j = 0; j < i; j++) + hi_msg_queue_sw_uninit(hmc, j); + + iounmap(hmc->reg_base); + return ret; +} + +void hi_msg_core_uninit(struct hi_msg_core *hmc) +{ + int i; + + hi_msg_debugfs_uninit(hmc); + hi_msg_queue_irq_uninit(hmc); + hi_msg_reset_queue(hmc); + + for (i = 0; i < MSGQ_NUM; i++) + hi_msg_queue_sw_uninit(hmc, i); + + iounmap(hmc->reg_base); +} + +void hi_msg_sqe_init(struct hi_msg_sqe *sqe, int msn, struct msg_info *info, + int task_type, u8 code) +{ + bool idev_flag = false; + + sqe->task_type = (u8)task_type; + /* ub bus controller & idev 's local is 1 */ + if (info->uent) { + if (info->ubc != info->uent && + uent_type(info->uent) == UB_TYPE_ICONTROLLER) + idev_flag = true; + + sqe->local = (info->ubc == info->uent || idev_flag) ? 1 : 0; + if (sqe->local) + sqe->dev_type = idev_flag ? TYPE_IDEV : TYPE_BUS_CONTROLLER; + } + + /* only msg need icrc */ + sqe->icrc = (task_type == PROTOCOL_MSG) ? 1 : 0; + sqe->opcode = code; + sqe->p_len = info->req_pkt_size; + sqe->msn = (u16)msn; + /* p_addr init inside submit */ +} + +int hi_msg_cq_poll(struct hi_msg_core *hmc, int task_type, u16 msn) +{ + struct hi_msg_queue *cq = &hmc->queue[MSG_CQ]; + struct hi_msg_cqe *cqe; + unsigned long flags; + int i, cnt, idx; + + spin_lock_irqsave(&cq->lock, flags); + cq->pi = hi_msg_reg_read(hmc, CQ_PI); + if (cq->pi == cq->ci) { + spin_unlock_irqrestore(&cq->lock, flags); + return -EAGAIN; + } + + cnt = q_used_cnt(cq); + for (i = 0; i < cnt; i++) { + idx = q_ptr_idx(cq, ci, i); + cqe = cq_entry(hmc, idx); + if (cqe->msn == msn && cqe->task_type == (u8)task_type && + (task_type != PROTOCOL_MSG || cqe->type == MSG_RSP)) { + spin_unlock_irqrestore(&cq->lock, flags); + return idx; + } + } + spin_unlock_irqrestore(&cq->lock, flags); + return -EAGAIN; +} + +int hi_message_cqe_check(struct device *dev, struct hi_msg_sqe *sqe, + struct hi_msg_cqe *cqe, u16 rsp_pkt_size) +{ + if (cqe->status != CQE_SUCCESS) { + dev_err(dev, "cqe fail status=%u\n", cqe->status); + return -EINVAL; + } + + if (cqe->p_len > rsp_pkt_size || cqe->p_len > HI_MSG_RQE_SIZE) { + dev_err(dev, "cqe p_len %#x invalid, rsp_pkt_size %#x\n", + cqe->p_len, rsp_pkt_size); + return -EINVAL; + } + + if (sqe->task_type == PROTOCOL_MSG) { + if (cqe->p_len < MSG_PKT_HEADER_SIZE) { + dev_err(dev, "cqe p_len %u is less than msg pkt header size.\n", + cqe->p_len); + return -EINVAL; + } + if ((sqe->opcode >> 1) == (cqe->opcode >> 1)) + return 0; + } else { + if (sqe->opcode == cqe->opcode) + return 0; + } + + dev_err(dev, "opcode sqe %#x != cqe %#x\n", sqe->opcode, cqe->opcode); + return -EINVAL; +} + +void hi_msg_rqe_get(struct hi_msg_core *hmc, void *buf, struct hi_msg_cqe *cqe) +{ + u16 rqe_end_index, rqe_high_size, rqe_low_size, pi, depth; + u32 p_len; + + depth = hmc->queue[MSG_RQ].depth; + pi = cqe->rq_pi; + p_len = cqe->p_len; + + rqe_end_index = pi + DIV_ROUND_UP(p_len, HI_MSG_RQE_SIZE); + if (rqe_end_index <= depth) { + memcpy(buf, rq_entry(hmc, pi), p_len); + return; + } + + /* overlap scenario */ + rqe_high_size = (depth - pi) * HI_MSG_RQE_SIZE; + rqe_low_size = p_len - rqe_high_size; + memcpy(buf, rq_entry(hmc, pi), rqe_high_size); + memcpy(buf + rqe_high_size, rq_entry(hmc, 0), rqe_low_size); +} + +void hi_msg_rq_update(struct hi_msg_core *hmc, int cq_idx) +{ + struct hi_msg_queue *rq = &hmc->queue[MSG_RQ]; + struct hi_msg_cqe *cqe; + + cqe = cq_entry(hmc, cq_idx); + rq->ci = (cqe->rq_pi + DIV_ROUND_UP(cqe->p_len, HI_MSG_RQE_SIZE)) % + rq->depth; + wmb(); /* Ensure the register is written correctly. */ + hi_msg_reg_write(hmc, RQ_CI, rq->ci); +} + +void hi_msg_set_pkt_msn(struct msg_info *info, int task_type, u16 msn, + u8 msgq_id) +{ + struct enum_topo_query_req *req; + struct msg_pkt_header *header; + void *pkt = info->req_packet; + size_t header_sz; + + switch (task_type) { + case PROTOCOL_ENUM: + header_sz = ENUM_PKT_HEADER_SIZE + + calc_enum_pld_header_size( + (struct enum_pld_scan_header + *)(pkt + ENUM_PKT_HEADER_SIZE), + true); + req = (struct enum_topo_query_req *)(pkt + header_sz); + req->common.msn = msn; + req->common.msgq_id = msgq_id; + break; + case PROTOCOL_MSG: + header = (struct msg_pkt_header *)pkt; + header->src_tassn = msn; + header->msgq_id = msgq_id; + break; + default: + return; + } +} diff --git a/drivers/ub/ubus/vendor/hisilicon/msg-debugfs.c b/drivers/ub/ubus/vendor/hisilicon/msg-debugfs.c new file mode 100644 index 0000000000000000000000000000000000000000..495493188730c69344357a9e9a2d29bd9669405a --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/msg-debugfs.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi msg-d: " fmt + +#include +#include "../../ubus.h" +#include "../../msg.h" +#include "../../enum.h" +#include "hisi-msg.h" +#include "hisi-ubus.h" + +#define REG_NAME_LEN 16 +struct hi_msg_reg_info_s { + char name[REG_NAME_LEN]; + u16 offset; +}; + +static struct hi_msg_reg_info_s hi_msg_reg_info[] = { + {"sq_pi", SQ_PI}, + {"sq_ci", SQ_CI}, + {"sq_depth", SQ_DEPTH}, + {"sq_int_mask", SQ_INT_MSK}, + {"rq_pi", RQ_PI}, + {"rq_ci", RQ_CI}, + {"rq_depth", RQ_DEPTH}, + {"rq_entry_size", RQ_ENTRY_SIZE}, + {"cq_pi", CQ_PI}, + {"cq_ci", CQ_CI}, + {"cq_depth", CQ_DEPTH}, + {"cq_int_mask", CQ_INT_MASK}, + {"cq_int_status", CQ_INT_STATUS}, + {"cq_int_ro", CQ_INT_RO}, + {"q_int_sel", MSGQ_INT_SEL} +}; + +static ssize_t hi_msg_reg_info_read(struct file *file, char __user *ubuf, + size_t size, loff_t *loff) +{ + struct hi_msg_core *hmc = + (struct hi_msg_core *)file->f_inode->i_private; + struct hi_msg_reg_info_s *reg; + size_t len = 0; + int ret, i; + char *buf; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + reg = hi_msg_reg_info; + for (i = 0; i < ARRAY_SIZE(hi_msg_reg_info); i++) { + len += (size_t)scnprintf(buf + len, SZ_4K - len, "%s:%#x\n", + reg->name, + hi_msg_reg_read(hmc, reg->offset)); + reg++; + } + + ret = simple_read_from_buffer(ubuf, size, loff, buf, len); + kfree(buf); + return ret; +} + +static const struct file_operations hi_msg_reg_info_ops = { + .read = hi_msg_reg_info_read, +}; + +static int q_num; +static u32 q_idx; +static DEFINE_MUTEX(q_lock); + +static size_t hi_msg_buf_assemble(char *buf, size_t size, u32 *addr, int count) +{ + int i, len = 0; + +#define DW_EACH_LINE 4 + for (i = 0; i < count; i++) { + if (i % DW_EACH_LINE == 0) + len += scnprintf(buf + len, size - len, "\n"); + len += scnprintf(buf + len, size - len, " %08x", *(addr + i)); + } + + len += scnprintf(buf + len, size - len, "\n"); + + return len; +} + +static ssize_t hi_msg_q_entry_info_read(struct file *file, char __user *ubuf, + size_t size, loff_t *loff) +{ + struct hi_msg_core *hmc = + (struct hi_msg_core *)file->f_inode->i_private; + int ret, num, entry_size; + struct hi_msg_sqe *sqe; + struct hi_msg_cqe *cqe; + u32 *entry, idx; + size_t len = 0; + char *buf; + + buf = kzalloc(SZ_8K, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&q_lock); + num = q_num; + idx = q_idx; + mutex_unlock(&q_lock); + + switch (num) { + case MSG_SQ: + sqe = &hmc->queue[num].sqe[idx]; + + entry = (u32 *)sqe; + entry_size = HI_MSG_SQE_SIZE; + break; + case MSG_CQ: + default: + cqe = &hmc->queue[num].cqe[idx]; + + entry = (u32 *)cqe; + entry_size = HI_MSG_CQE_SIZE; + break; + } + + len += (size_t)scnprintf(buf + len, SZ_8K - len, "%s[%#x]:", + (num == MSG_CQ) ? "cq" : "sq", idx); + len += hi_msg_buf_assemble(buf + len, SZ_8K - len, entry, + entry_size / sizeof(u32)); + + ret = simple_read_from_buffer(ubuf, size, loff, buf, len); + kfree(buf); + return ret; +} + +static ssize_t hi_msg_q_entry_info_write(struct file *file, + const char __user *ubuf, + size_t size, loff_t *loff) +{ + struct hi_msg_core *hmc = + (struct hi_msg_core *)file->f_inode->i_private; +#define BUF_SIZE 32 + int len, num, fields; + char buf[BUF_SIZE]; + u32 idx; + + if (*loff != 0) + return 0; + + if (size >= BUF_SIZE) + return -ENOSPC; + + len = simple_write_to_buffer(buf, BUF_SIZE - 1, loff, ubuf, size); + if (len < 0) + return len; + + buf[len] = '\0'; + fields = sscanf(buf, "%d %x", &num, &idx); + if (fields != 2) + return -EINVAL; + + if ((num != MSG_SQ && num != MSG_CQ) || idx >= hmc->queue[num].depth) { + pr_err("msgq num %d or queue entry idx %#x is invalid\n", + num, idx); + return -EINVAL; + } + + mutex_lock(&q_lock); + q_num = num; + q_idx = idx; + mutex_unlock(&q_lock); + return size; +} + +static const struct file_operations hi_msg_q_entry_info_ops = { + .read = hi_msg_q_entry_info_read, + .write = hi_msg_q_entry_info_write, +}; + +void hi_msg_debugfs_init(struct hi_msg_core *hmc) +{ + struct ub_bus_controller *ubc = container_of(hmc->dev, struct ub_bus_controller, + dev); + struct dentry *dir; + + dir = debugfs_create_dir(hmc->queue_name, ubc->debug_root); + debugfs_create_file("reg_info", 0400, dir, hmc, + &hi_msg_reg_info_ops); + debugfs_create_file("q_entry_info", 0600, dir, + hmc, &hi_msg_q_entry_info_ops); +} + +void hi_msg_debugfs_uninit(struct hi_msg_core *hmc) +{ + struct ub_bus_controller *ubc = container_of(hmc->dev, struct ub_bus_controller, + dev); + + debugfs_lookup_and_remove(hmc->queue_name, ubc->debug_root); +} + +static void hi_msg_sqe_dump(struct hi_msg_sqe *sqe) +{ + pr_err("sqe:\n"); + pr_err("type=%u local=%u dev=%u icrc=%u len=%#x msn=%#x off=%#x\n", + sqe->task_type, sqe->local, sqe->dev_type, sqe->icrc, + sqe->p_len, sqe->msn, sqe->p_addr); + + if (sqe->task_type == PROTOCOL_MSG) + pr_err("code=%#x sub=%#x rsp=%u\n", sqe->msg_code, + sqe->sub_msg_code, sqe->type); + else + pr_err("opcode=%#x\n", sqe->opcode); +} + +static void hi_msg_cqe_dump(struct hi_msg_cqe *cqe) +{ + pr_err("cqe:\n"); + pr_err("type=%u plen=%#x msn=%#x pi=%#x s=%u\n", cqe->task_type, + cqe->p_len, cqe->msn, cqe->rq_pi, cqe->status); + + if (cqe->task_type == PROTOCOL_MSG) + pr_err("code=%#x sub=%#x rsp=%u\n", cqe->msg_code, + cqe->sub_msg_code, cqe->type); + else + pr_err("opcode=%#x\n", cqe->opcode); +} + +static void hi_msg_header_dump(void *buf) +{ + struct msg_pkt_header *h = (struct msg_pkt_header *)buf; + struct msg_extended_header *eh = &h->msgetah; + struct compact_network_header *nh = &h->nth; + struct ub_link_header *lh = &h->ulh; + + pr_err("msg header:\n"); + pr_err("cfg=%u dcna=%#x scna=%#x nlp=%u mgmt=%u\n", + lh->cfg, nh->dcna, nh->scna, nh->nth_nlp, nh->mgmt); + pr_err("tp_opcode=%u tp_nlp=%u upi=%#x seid=%#x deid=%#x\n", + h->tp_opcode, + h->ctph_nlp, h->upi, eid_gen(h->seid_h, h->seid_l), h->deid); + pr_err("msn=%#x ta_opcode=%#x msgq_id=%u\n", h->src_tassn, h->ta_opcode, + h->msgq_id); + pr_err("code=%#x sub=%#x rsp=%u plen=%#x s=%u\n", eh->msg_code, + eh->sub_msg_code, eh->type, eh->plen, eh->rsp_status); +} + +static void hi_enum_header_dump(void *buf) +{ + struct enum_pkt_header *h = (struct enum_pkt_header *)buf; + struct compact_network_header *nh = &h->cnth; + struct ub_link_header *lh = &h->ulh; + + pr_err("enum header:\n"); + pr_err("cfg=%u dcna=%#x scna=%#x nlp=%u mgmt=%u upi=%#x\n", + lh->cfg, nh->dcna, nh->scna, nh->nth_nlp, nh->mgmt, h->upi); +} + +static void hi_msg_pld_dump(void *buf, int task_type, int len) +{ + switch (task_type) { + case PROTOCOL_MSG: + if (len < MSG_PKT_HEADER_SIZE) + return; + hi_msg_header_dump(buf); + break; + case PROTOCOL_ENUM: + if (len < ENUM_PKT_HEADER_SIZE) + return; + hi_enum_header_dump(buf); + break; + case HISI_PRIVATE: + /* Don't print */ + break; + default: + pr_err("bad task_type:%d\n", task_type); + } +} + +void ub_msg_dump_sq(struct hi_msg_sqe *sqe, void *sq_pld) +{ + if (sqe) + hi_msg_sqe_dump(sqe); + + if (sq_pld && sqe) + hi_msg_pld_dump(sq_pld, sqe->task_type, sqe->p_len); +} + +void ub_msg_dump_cq(struct hi_msg_cqe *cqe, void *rqe) +{ + if (cqe) + hi_msg_cqe_dump(cqe); + + if (rqe && cqe) + hi_msg_pld_dump(rqe, cqe->task_type, cqe->p_len); +} diff --git a/drivers/ub/ubus/vendor/hisilicon/msg.c b/drivers/ub/ubus/vendor/hisilicon/msg.c new file mode 100644 index 0000000000000000000000000000000000000000..53f3b1b7d660103d7dd41667b70770e555faf2b4 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/msg.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi msg: " fmt + +#include + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../enum.h" +#include "vdm.h" +#include "hisi-msg.h" + +#define MSGQ_INT1 1 +#define to_ub_bus_controller(d) container_of(d, struct ub_bus_controller, dev) + +struct hi_msg_sqe_pld { + char packet[HI_MSG_SQE_PLD_SIZE]; +}; + +struct timeout_msg_info { + struct list_head node; + u16 msn; + u8 task_type; + u8 age; +}; +#define TIMEOUT_MSG_INFO_SZ sizeof(struct timeout_msg_info) + +struct hi_message_device { + struct hi_msg_core hmc; + struct timer_list poll_timer; + struct timer_list timeout_msg_timer; + struct list_head timeout_msg_list; + spinlock_t timeout_msg_lock; + struct hi_cqe_state *cqe_state; + atomic_t msg_in_flight_cnt; + struct message_device mdev; +}; + +#define to_hi_message_device(dev) \ + container_of(dev, struct hi_message_device, mdev) + +enum hi_cq_sw_state { CQ_SW_INIT, CQ_SW_HANDLED }; + +struct hi_cqe_state { + u8 state; + u8 age; +}; +#define HI_CQE_STATE_SZ sizeof(struct hi_cqe_state) + +static inline void cqe_state_set(struct hi_message_device *hmd, int idx, + u8 state) +{ + hmd->cqe_state[idx].state = state; + hmd->cqe_state[idx].age = 0; +} +#define cqe_state_get(hmd, idx) ((hmd)->cqe_state[idx].state) +#define cqe_age_inc(hmd, idx) (((hmd)->cqe_state[idx].age)++) +#define cqe_age_get(hmd, idx) ((hmd)->cqe_state[idx].age) + +#define HI_MSG_CQ_POLL_PERIOD 100 +#define HI_TIMEOUT_MSG_POLL_PERIOD 500 +#define HI_MSG_AGING_PERIOD 2 +#define MAX_CQ_POLL_PER_TIME 64 + +#define MSG_MAX (HI_SQ_CFG_DEPTH - 1) +#define q_left_cnt(q) ((q)->depth - q_used_cnt((q)) - 1) +#define sq_sw_left(hmd) (MSG_MAX - atomic_read(&(hmd)->msg_in_flight_cnt)) +#define sq_pld_entry_off(hmd, idx) \ + ((HI_MSG_SQE_SIZE * HI_SQ_CFG_DEPTH) + (HI_MSG_SQE_PLD_SIZE * (idx))) +#define sq_pld_entry_sw(hmd, idx) \ + ((void *)&((hmd)->hmc.queue[MSG_SQ].sqe[HI_SQ_CFG_DEPTH]) + \ + (HI_MSG_SQE_PLD_SIZE * (idx))) + +#define MSGQ_TIMEOUT (HZ * msg_wait / 1000) + +#define MSN_SIZE 65535 +DEFINE_SPINLOCK(msn_msg_lock); +DECLARE_BITMAP(msn_msg, MSN_SIZE); +DEFINE_SPINLOCK(msn_enum_lock); +DECLARE_BITMAP(msn_enum, MSN_SIZE); +DEFINE_SPINLOCK(msn_private_lock); +DECLARE_BITMAP(msn_private, MSN_SIZE); + +/* Return 0 represents failure, return 1-65535 represents success */ +static u16 hi_msn_get(int type) +{ + unsigned long flags, find, tmp, size, *addr; + static u16 msn[TASK_TYPE_NUM] = {}; + spinlock_t *lock; + u16 ret; + + find = msn[type]; + + if (type == PROTOCOL_MSG) { + addr = msn_msg; + lock = &msn_msg_lock; + } else if (type == PROTOCOL_ENUM) { + addr = msn_enum; + lock = &msn_enum_lock; + } else { /* hisi private */ + addr = msn_private; + lock = &msn_private_lock; + } + + spin_lock_irqsave(lock, flags); + if (unlikely(test_bit(find, addr))) { + size = MSN_SIZE; + tmp = find; + find = find_next_zero_bit(addr, size, find); + if (unlikely(find == size)) { + size = tmp; + find = find_next_zero_bit(addr, size, 0); + if (unlikely(find == size)) { + ret = 0; + goto out; + } + } + } + + set_bit(find, addr); + msn[type] = (find + 1) % MSN_SIZE; + ret = find + 1; /* find bit is 0~65534, ret use 1~65535 */ +out: + spin_unlock_irqrestore(lock, flags); + return ret; +} + +static void hi_msn_put(int type, u16 msn) +{ + unsigned long flags, *addr; + spinlock_t *lock; + + if (msn == 0 || type >= TASK_TYPE_NUM) { + pr_warn("invalid msn or type\n"); + return; + } + + if (type == PROTOCOL_MSG) { + addr = msn_msg; + lock = &msn_msg_lock; + } else if (type == PROTOCOL_ENUM) { + addr = msn_enum; + lock = &msn_enum_lock; + } else { /* hisi private */ + addr = msn_private; + lock = &msn_private_lock; + } + + spin_lock_irqsave(lock, flags); + clear_bit(msn - 1, addr); + spin_unlock_irqrestore(lock, flags); +} + +static void hi_msgq_hw_status(struct hi_msg_core *hmc) +{ + dev_err_ratelimited( + hmc->dev, "sq pi=%#04x, ci=%#04x\ncq pi=%#04x, ci=%#04x\nrq pi=%#04x, ci=%#04x\n", + hi_msg_reg_read(hmc, SQ_PI), hi_msg_reg_read(hmc, SQ_CI), + hi_msg_reg_read(hmc, CQ_PI), hi_msg_reg_read(hmc, CQ_CI), + hi_msg_reg_read(hmc, RQ_PI), hi_msg_reg_read(hmc, RQ_CI)); +} + +static void hi_msgq_sw_status(struct hi_message_device *hmd) +{ + dev_err_ratelimited(hmd->hmc.dev, "sq in flight msg:%d\n", + atomic_read(&hmd->msg_in_flight_cnt)); +} + +static void hi_msgq_status(struct hi_message_device *hmd) +{ + hi_msgq_hw_status(&hmd->hmc); + hi_msgq_sw_status(hmd); +} + +static int hi_msg_get_sq_idle_num(struct hi_message_device *hmd, int num, + bool tx) +{ + struct hi_msg_queue *sq = &hmd->hmc.queue[MSG_SQ]; + int real, sw_left, hw_left; + + hw_left = q_left_cnt(sq); + sw_left = sq_sw_left(hmd); + real = num; + + /* Assure message in flight not overflow, just tx need check */ + if (tx && real > sw_left) + real = sw_left; + + /* If not enough, update ci by read hardware */ + if (real > hw_left) { + sq->ci = hi_msg_reg_read(&hmd->hmc, SQ_CI); + hw_left = q_left_cnt(sq); + if (real > hw_left) + real = hw_left; + } + + return real; +} + +static int hi_msg_sq_submit(struct hi_message_device *hmd, + struct hi_msg_sqe *sqe, struct hi_msg_sqe_pld *pld, + int num, bool wait) +{ + struct hi_msg_queue *sq = &hmd->hmc.queue[MSG_SQ]; + struct hi_msg_core *hmc = &hmd->hmc; + struct hi_msg_sqe *tar_sqe; + unsigned long flags; + int i, real; + u16 sqe_idx; + + spin_lock_irqsave(&sq->lock, flags); + + real = hi_msg_get_sq_idle_num(hmd, num, wait); + if (!real) { + spin_unlock_irqrestore(&sq->lock, flags); + return 0; + } + + for (i = 0; i < real; i++) { + sqe_idx = q_ptr_idx(sq, pi, i); + tar_sqe = sq->sqe + sqe_idx; + (sqe + i)->p_addr = sq_pld_entry_off(hmd, sqe_idx); + memcpy(tar_sqe, sqe + i, HI_MSG_SQE_SIZE); + memcpy(sq_pld_entry_sw(hmd, sqe_idx), pld[i].packet, + (sqe + i)->p_len); + } + + sq->pi = q_ptr_idx(sq, pi, real); + wmb(); /* Ensure the register is written correctly. */ + hi_msg_reg_write(hmc, SQ_PI, sq->pi); + if (wait) + atomic_add(real, &hmd->msg_in_flight_cnt); + + spin_unlock_irqrestore(&sq->lock, flags); + + return real; +} + +static int hi_msg_sync_wait(struct hi_message_device *hmd, int task_type, + u16 msn, u64 duration, bool flag) +{ +#define SLEEP_MIN_US 1000 +#define SLEEP_MAX_US 2000 + u64 end_time = get_jiffies_64() + duration; + struct timeout_msg_info *timeout_msg; + struct hi_msg_core *hmc = &hmd->hmc; + unsigned long flags; + int idx; + + do { + idx = hi_msg_cq_poll(hmc, task_type, msn); + if (idx >= 0) + return idx; + + if (flag) + usleep_range(SLEEP_MIN_US, SLEEP_MAX_US); + } while (!time_after64(get_jiffies_64(), end_time)); + + timeout_msg = kzalloc(TIMEOUT_MSG_INFO_SZ, GFP_ATOMIC); + if (!timeout_msg) + return -ENOMEM; + + timeout_msg->task_type = task_type; + timeout_msg->msn = msn; + spin_lock_irqsave(&hmd->timeout_msg_lock, flags); + list_add_tail(&timeout_msg->node, &hmd->timeout_msg_list); + spin_unlock_irqrestore(&hmd->timeout_msg_lock, flags); + dev_err(hmc->dev, "task %d msn %#x wait cqe timeout\n", task_type, msn); + return -ETIMEDOUT; +} + +static bool hi_msg_has_rx(struct hi_message_device *hmd, int ci, int pi) +{ + struct hi_msg_queue *cq = &hmd->hmc.queue[MSG_CQ]; + struct hi_msg_core *hmc = &hmd->hmc; + struct hi_msg_cqe *cqe; + int depth, cnt, i, idx; + + depth = hmc->queue[MSG_CQ].depth; + cnt = (pi + depth - ci) % depth; + + for (i = 0; i < cnt; i++) { + idx = q_ptr_idx(cq, ci, i); + cqe = cq_entry(hmc, idx); + if (cqe_state_get(hmd, idx) != CQ_SW_HANDLED && + cqe->task_type == PROTOCOL_MSG && cqe->type == MSG_REQ) + return true; + } + + return false; +} + +static void hi_msg_cq_update(struct hi_message_device *hmd) +{ + struct hi_msg_queue *cq = &hmd->hmc.queue[MSG_CQ]; + struct hi_msg_core *hmc = &hmd->hmc; + int i, cnt, idx, release_cnt = 0; + struct hi_msg_cqe *cqe; + unsigned long flags; + u32 cq_pi; + + spin_lock_irqsave(&cq->lock, flags); + if (cq->ci == cq->pi) { + spin_unlock_irqrestore(&cq->lock, flags); + return; + } + + cnt = q_used_cnt(cq); + for (i = 0; i < cnt; i++) { + idx = q_ptr_idx(cq, ci, i); + if (cqe_state_get(hmd, idx) != CQ_SW_HANDLED) + break; + + cqe = cq_entry(hmc, idx); + if (cqe->task_type != PROTOCOL_MSG || cqe->type == MSG_RSP) { + hi_msn_put(cqe->task_type, cqe->msn); + release_cnt++; + } + + cqe_state_set(hmd, idx, CQ_SW_INIT); + } + if (i == 0) { + spin_unlock_irqrestore(&cq->lock, flags); + return; + } + + hi_msg_rq_update(hmc, q_ptr_idx(cq, ci, i - 1)); + + cq->ci = q_ptr_idx(cq, ci, i); + wmb(); /* Ensure the register is written correctly. */ + hi_msg_reg_write(hmc, CQ_CI, cq->ci); + atomic_sub(release_cnt, &hmd->msg_in_flight_cnt); + + if (atomic_read(&hmc->cq_int_mask)) { + /* Get newest pi, but not store in cq->pi */ + cq_pi = hi_msg_reg_read(hmc, CQ_PI); + /* Check really need open interrupt */ + if (cq_pi == cq->ci || hi_msg_has_rx(hmd, cq->ci, cq_pi)) { + hi_msg_reg_write(hmc, CQ_INT_STATUS, 0x1); + /* spi line interrupt, unmask during _irqsave no impact */ + hi_msg_reg_write(hmc, CQ_INT_MASK, 0x0); + atomic_set(&hmc->cq_int_mask, 0); + } + } + + spin_unlock_irqrestore(&cq->lock, flags); +} + +static int hi_msg_cq_poller(struct hi_message_device *hmd) +{ + struct ub_bus_controller *ubc = to_ub_bus_controller(hmd->hmc.dev); + struct hi_msg_queue *cq = &hmd->hmc.queue[MSG_CQ]; + int cqe_cnt, i, idx, ret, handled_cnt = 0; + struct hi_msg_core *hmc = &hmd->hmc; + struct hi_msg_cqe *cqe; + unsigned long flags; + + spin_lock_irqsave(&cq->lock, flags); + cq->pi = hi_msg_reg_read(hmc, CQ_PI); + if (cq->pi == cq->ci) { + spin_unlock_irqrestore(&cq->lock, flags); + return -EAGAIN; + } + + cqe_cnt = q_used_cnt(cq); + for (i = 0; i < cqe_cnt; i++) { + idx = q_ptr_idx(cq, ci, i); + cqe = cq_entry(hmc, idx); + + if (cqe_state_get(hmd, idx) == CQ_SW_HANDLED) + continue; + + /* Now, just msg type has rx */ + if (cqe->task_type != PROTOCOL_MSG || cqe->type != MSG_REQ) + continue; + + if (cqe->p_len > HI_MSG_RQE_SIZE) { + dev_err(hmc->dev, "poller cqe p_len invalid\n"); + ub_msg_dump_cq(cqe, NULL); + goto handled; + } + + ret = message_rx_handler(ubc, rq_entry(hmc, cqe->rq_pi), + cqe->p_len); + if (ret) { + dev_err(hmc->dev, "poller rx msg failed, ret=%d\n", ret); + ub_msg_dump_cq(cqe, rq_entry(hmc, cqe->rq_pi)); + } +handled: + cqe_state_set(hmd, idx, CQ_SW_HANDLED); + handled_cnt++; + if (handled_cnt == MAX_CQ_POLL_PER_TIME) + break; + } + + spin_unlock_irqrestore(&cq->lock, flags); + return handled_cnt; +} + +static void hi_msg_cq_handle_poll_timer(struct timer_list *timer) +{ + struct hi_message_device *hmd; + + hmd = container_of(timer, struct hi_message_device, poll_timer); + if (hi_msg_cq_poller(hmd) > 0) + hi_msg_cq_update(hmd); + mod_timer(timer, jiffies + msecs_to_jiffies(HI_MSG_CQ_POLL_PERIOD)); +} + +static void hi_msg_cq_poller_init(struct hi_message_device *hmd) +{ + if (hmd->hmc.virq) + return; + + struct timer_list *poll_timer = &hmd->poll_timer; + + timer_setup(poll_timer, hi_msg_cq_handle_poll_timer, 0); + poll_timer->expires = jiffies + msecs_to_jiffies(HI_MSG_CQ_POLL_PERIOD); + add_timer(poll_timer); +} + +static void hi_msg_cq_poller_uninit(struct hi_message_device *hmd) +{ + if (hmd->hmc.virq) + return; + + struct timer_list *poll_timer = &hmd->poll_timer; + + timer_shutdown_sync(poll_timer); +} + +static int hi_msg_queue_init(struct hi_message_device *hmd) +{ + struct hi_msg_core *hmc = &hmd->hmc; + size_t size; + int ret; + + size = HI_CQE_STATE_SZ * HI_CQ_CFG_DEPTH; + hmd->cqe_state = kzalloc(size, GFP_KERNEL); + if (!hmd->cqe_state) { + dev_err(hmc->dev, "cqe state memory failed\n"); + return -ENOMEM; + } + + ret = hi_msg_core_init(hmc, MSGQ_USER_BUS_DRV); + if (ret) { + kfree(hmd->cqe_state); + hmd->cqe_state = NULL; + return ret; + } + + hi_msg_cq_poller_init(hmd); + + return 0; +} + +static void hi_msg_queue_uninit(struct hi_message_device *hmd) +{ + hi_msg_cq_poller_uninit(hmd); + hi_msg_core_uninit(&hmd->hmc); + kfree(hmd->cqe_state); +} + +static bool pkt_plen_valid(void *pkt, u16 pkt_size, int task_type) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + + if (pkt_size > HI_MSG_SQE_PLD_SIZE) { + pr_err("pkt_size %#x over\n", pkt_size); + return false; + } + + if (task_type == PROTOCOL_MSG && + ((pkt_size < MSG_PKT_HEADER_SIZE) || + (header->msgetah.plen > PLD_SIZE_MAX) || + (header->msgetah.plen != (pkt_size - MSG_PKT_HEADER_SIZE)))) { + pr_err("msg plen %#x, pkt_size %#x invalid\n", + header->msgetah.plen, pkt_size); + return false; + } + + if (task_type == PROTOCOL_ENUM && + ((pkt_size < ENUM_PKT_HEADER_SIZE) || + (pkt_size - ENUM_PKT_HEADER_SIZE > PLD_SIZE_MAX))) { + pr_err("enum pkt_size %#x invalid\n", pkt_size); + return false; + } + + return true; +} + +static int hi_message_sync(struct message_device *mdev, struct msg_info *info, + int task_type, u8 code, bool flag) +{ + struct hi_message_device *hmd = to_hi_message_device(mdev); + struct hi_msg_core *hmc = &hmd->hmc; + struct hi_msg_sqe sqe = {}; + struct hi_msg_cqe *cqe; + int ret, msn, cnt; + int cq_idx; + + if (!pkt_plen_valid(info->req_packet, info->req_pkt_size, task_type)) + return -EINVAL; + + msn = hi_msn_get(task_type); + if (msn == 0) { + dev_err(hmc->dev, "task type %d alloc msn failed\n", task_type); + return -ENOSPC; + } + + hi_msg_sqe_init(&sqe, msn, info, task_type, code); + hi_msg_set_pkt_msn(info, task_type, msn, hmc->user); + + cnt = hi_msg_sq_submit( + hmd, &sqe, (struct hi_msg_sqe_pld *)info->req_packet, 1, true); + if (cnt != 1) { + dev_err(hmc->dev, "sq submit failed\n"); + hi_msgq_status(hmd); + hi_msn_put(task_type, msn); + return -ENOSPC; + } + + cq_idx = hi_msg_sync_wait(hmd, task_type, msn, MSGQ_TIMEOUT, flag); + if (cq_idx < 0) { + if (cq_idx == -ENOMEM) + hi_msn_put(task_type, msn); + ub_msg_dump_sq(&sqe, info->req_packet); + return cq_idx; + } + + cqe = cq_entry(hmc, cq_idx); + ret = hi_message_cqe_check(hmc->dev, &sqe, cqe, info->rsp_pkt_size); + if (!ret) { + hi_msg_rqe_get(hmc, info->rsp_packet, cqe); + info->actual_rsp_size = cqe->p_len; + } else { + ub_msg_dump_sq(&sqe, info->req_packet); + ub_msg_dump_cq(cqe, NULL); + } + + cqe_state_set(hmd, cq_idx, CQ_SW_HANDLED); + hi_msg_cq_update(hmd); + return ret; +} + +int hi_message_sync_request(struct message_device *mdev, struct msg_info *info, + u8 code) +{ + return hi_message_sync(mdev, info, PROTOCOL_MSG, code, false); +} + +static int hi_message_sync_enum(struct message_device *mdev, + struct msg_info *info, u8 cmd) +{ + return hi_message_sync(mdev, info, PROTOCOL_ENUM, cmd, false); +} + +static int hi_message_response(struct message_device *mdev, + struct msg_info *info, u8 code) +{ + struct hi_message_device *hmd = to_hi_message_device(mdev); + struct hi_msg_sqe sqe = {}; + struct msg_pkt_header *header; + void *pkt = info->req_packet; + int cnt; + + if (!pkt_plen_valid(info->req_packet, info->req_pkt_size, PROTOCOL_MSG)) + return -EINVAL; + + header = (struct msg_pkt_header *)pkt; + hi_msg_sqe_init(&sqe, header->src_tassn, info, PROTOCOL_MSG, code); + + cnt = hi_msg_sq_submit( + hmd, &sqe, (struct hi_msg_sqe_pld *)info->req_packet, 1, false); + if (cnt != 1) { + dev_err(hmd->hmc.dev, "rsp sq submit failed\n"); + return -ENOSPC; + } + + return 0; +} + +static int hi_message_send(struct message_device *mdev, struct msg_info *info, + u8 code) +{ + struct hi_message_device *hmd = to_hi_message_device(mdev); + struct hi_msg_core *hmc = &hmd->hmc; + struct hi_msg_sqe sqe = {}; + int msn, cnt; + int ret = 0; + + if (!pkt_plen_valid(info->req_packet, info->req_pkt_size, PROTOCOL_MSG)) + return -EINVAL; + + msn = hi_msn_get(PROTOCOL_MSG); + if (msn == 0) { + dev_err(hmc->dev, "alloc msn failed\n"); + return -ENOSPC; + } + + hi_msg_sqe_init(&sqe, msn, info, PROTOCOL_MSG, code); + hi_msg_set_pkt_msn(info, PROTOCOL_MSG, msn, hmc->user); + + cnt = hi_msg_sq_submit( + hmd, &sqe, (struct hi_msg_sqe_pld *)info->req_packet, 1, false); + if (cnt != 1) { + dev_err(hmc->dev, "sq submit failed\n"); + hi_msgq_status(hmd); + ret = -ENOSPC; + } + + hi_msn_put(PROTOCOL_MSG, msn); + return ret; +} + +/* For some special message need sleep, work in non-atomic context */ +int hi_message_sync_request_sched(struct message_device *mdev, + struct msg_info *info, u8 code) +{ + return hi_message_sync(mdev, info, PROTOCOL_MSG, code, true); +} + +int hi_message_private(struct message_device *mdev, struct msg_info *info, + u8 opcode) +{ + int ret, i = 1; + + while (1) { + ret = hi_message_sync(mdev, info, HISI_PRIVATE, opcode, false); + if (ret != -ETIMEDOUT) + return ret; + + i++; + + if (!msg_retry || i > RETRY_COUNT) + return -ETIMEDOUT; + } +} + +static struct message_ops hi_message_ops = { + .sync_request = hi_message_sync_request, + .response = hi_message_response, + .sync_enum = hi_message_sync_enum, + .vdm_rx_handler = hi_vdm_rx_msg_handler, + .send = hi_message_send, +}; + +static void hi_bus_drv_msg_irq_handler(struct hi_msg_core *hmc) +{ + struct hi_message_device *hmd = + container_of(hmc, struct hi_message_device, hmc); + if (hi_msg_cq_poller(hmd) > 0) + hi_msg_cq_update(hmd); +} + +static void hi_timeout_msg_ageing(struct hi_message_device *hmd) +{ + struct timeout_msg_info *msg, *tmp; + + list_for_each_entry_safe(msg, tmp, &hmd->timeout_msg_list, node) { + if (msg->age >= HI_MSG_AGING_PERIOD) { + hi_msn_put(msg->task_type, msg->msn); + atomic_sub(1, &hmd->msg_in_flight_cnt); + list_del(&msg->node); + dev_err(hmd->hmc.dev, + "release aged timeout type=%u msn=%#x\n", + msg->task_type, msg->msn); + kfree(msg); + continue; + } + msg->age++; + } +} + +static bool hi_cqe_ageing(struct hi_message_device *hmd, int idx) +{ + struct hi_msg_core *hmc = &hmd->hmc; + struct ub_bus_controller *ubc; + struct hi_msg_cqe *cqe; + int ret; + + if (cqe_state_get(hmd, idx) == CQ_SW_HANDLED) + return false; + + if (cqe_age_get(hmd, idx) < HI_MSG_AGING_PERIOD) { + cqe_age_inc(hmd, idx); + return false; + } + + cqe = cq_entry(hmc, idx); + if (cqe->task_type == PROTOCOL_MSG && cqe->type == MSG_REQ) { + ubc = to_ub_bus_controller(hmc->dev); + + if (cqe->p_len > HI_MSG_RQE_SIZE) { + dev_err(hmc->dev, "ageing cqe p_len invalid\n"); + ub_msg_dump_cq(cqe, NULL); + return true; + } + + ret = message_rx_handler(ubc, rq_entry(hmc, cqe->rq_pi), + cqe->p_len); + if (ret) { + dev_err(hmc->dev, "rx msg failed, ret=%d\n", ret); + ub_msg_dump_cq(cqe, rq_entry(hmc, cqe->rq_pi)); + } + + dev_warn(hmc->dev, "interrupt dont up, process unhandled cqe, idx=%d type=%u msn=%#x opcode=%#x\n", + idx, cqe->task_type, cqe->msn, cqe->opcode); + } else { + dev_err(hmc->dev, "reset unhandled cqe, idx=%d type=%u msn=%#x\n", + idx, cqe->task_type, cqe->msn); + ub_msg_dump_cq(cqe, rq_entry(hmc, cqe->rq_pi)); + } + + return true; +} + +static bool hi_is_timeout_msg(struct hi_message_device *hmd, int idx) +{ + struct timeout_msg_info *msg, *tmp; + struct hi_msg_core *hmc = &hmd->hmc; + struct hi_msg_cqe *cqe; + + cqe = cq_entry(hmc, idx); + + list_for_each_entry_safe(msg, tmp, &hmd->timeout_msg_list, node) { + if (cqe->msn == msg->msn && cqe->task_type == msg->task_type && + (cqe->task_type != PROTOCOL_MSG || cqe->type == MSG_RSP)) { + list_del(&msg->node); + dev_err(hmc->dev, + "Timeout Message Processed, task=%u msn=%#x\n", + msg->task_type, msg->msn); + kfree(msg); + return true; + } + } + return false; +} + +static int hi_msg_timeout_poller(struct hi_message_device *hmd) +{ + unsigned long cq_flags, timeout_msg_flags; + struct hi_msg_queue *cq; + int handled_cnt = 0; + int cqe_cnt, i, idx; + + cq = &hmd->hmc.queue[MSG_CQ]; + spin_lock_irqsave(&cq->lock, cq_flags); + spin_lock_irqsave(&hmd->timeout_msg_lock, timeout_msg_flags); + cq->pi = hi_msg_reg_read(&hmd->hmc, CQ_PI); + + hi_timeout_msg_ageing(hmd); + + if (cq->pi == cq->ci) { + spin_unlock_irqrestore(&hmd->timeout_msg_lock, + timeout_msg_flags); + spin_unlock_irqrestore(&cq->lock, cq_flags); + return 0; + } + + cqe_cnt = q_used_cnt(cq); + for (i = 0; i < cqe_cnt; i++) { + idx = q_ptr_idx(cq, ci, i); + if (hi_cqe_ageing(hmd, idx) || hi_is_timeout_msg(hmd, idx)) { + cqe_state_set(hmd, idx, CQ_SW_HANDLED); + handled_cnt++; + if (handled_cnt == MAX_CQ_POLL_PER_TIME) + break; + } + } + + spin_unlock_irqrestore(&hmd->timeout_msg_lock, timeout_msg_flags); + spin_unlock_irqrestore(&cq->lock, cq_flags); + return handled_cnt; +} + +static void hi_timeout_msg_handle(struct timer_list *timer) +{ + struct hi_message_device *hmd; + + hmd = container_of(timer, struct hi_message_device, timeout_msg_timer); + if (hi_msg_timeout_poller(hmd) > 0) + hi_msg_cq_update(hmd); + mod_timer(timer, + jiffies + msecs_to_jiffies(HI_TIMEOUT_MSG_POLL_PERIOD)); +} + +static void hi_timeout_msg_poller_init(struct hi_message_device *hmd) +{ + struct timer_list *timer = &hmd->timeout_msg_timer; + + INIT_LIST_HEAD(&hmd->timeout_msg_list); + spin_lock_init(&hmd->timeout_msg_lock); + timer_setup(timer, hi_timeout_msg_handle, 0); + timer->expires = jiffies + msecs_to_jiffies(HI_TIMEOUT_MSG_POLL_PERIOD); + add_timer(timer); +} + +static void hi_timeout_msg_poller_uninit(struct hi_message_device *hmd) +{ + struct timer_list *timer = &hmd->timeout_msg_timer; + struct timeout_msg_info *time_out_msg, *tmp; + + timer_shutdown_sync(timer); + list_for_each_entry_safe(time_out_msg, tmp, &hmd->timeout_msg_list, + node) { + list_del(&time_out_msg->node); + kfree(time_out_msg); + } +} + +int hi_msg_device_probe(struct ub_bus_controller *ubc) +{ + struct device *dev = &ubc->dev; + struct hi_message_device *hmd; + struct hi_msg_core *hmc; + int ret; + + hmd = kzalloc(sizeof(*hmd), GFP_KERNEL); + if (!hmd) + return -ENOMEM; + + hmc = &hmd->hmc; + hmc->dev = dev; + hmc->q_addr = ubc->attr.queue_addr; + hmc->q_size = HI_MSGQ_SIZE; + hmc->virq = ubc->queue_virq; + hmc->intx = MSGQ_INT1; + hmc->irq_handler = hi_bus_drv_msg_irq_handler; + snprintf(hmc->queue_name, HI_MSG_INT_NAME_LEN, "hi_msgq%u-%d", + ubc->ctl_no, MSGQ_USER_BUS_DRV); + + + ret = hi_msg_queue_init(hmd); + if (ret) { + dev_err(dev, "init message queue failed\n"); + goto queue_init_fail; + } + + hi_timeout_msg_poller_init(hmd); + + message_device_set_ops(&hmd->mdev, &hi_message_ops); + message_device_set_fwnode(&hmd->mdev, dev->fwnode); + + ret = message_device_register(&hmd->mdev); + if (ret) { + dev_err(dev, "register message device failed\n"); + goto mdev_reg_fail; + } + + ubc->mdev = &hmd->mdev; + + return 0; + +mdev_reg_fail: + hi_timeout_msg_poller_uninit(hmd); + hi_msg_queue_uninit(hmd); +queue_init_fail: + kfree(hmd); + return ret; +} + +void hi_msg_device_remove(struct ub_bus_controller *ubc) +{ + struct hi_message_device *hmd = to_hi_message_device(ubc->mdev); + + ubc->mdev = NULL; + message_device_unregister(&hmd->mdev); + hi_timeout_msg_poller_uninit(hmd); + hi_msg_queue_uninit(hmd); + kfree(hmd); +} diff --git a/drivers/ub/ubus/vendor/hisilicon/vdm.c b/drivers/ub/ubus/vendor/hisilicon/vdm.c new file mode 100644 index 0000000000000000000000000000000000000000..d52ae6ffe02ba93291ff0e716245c68339eefc8e --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/vdm.c @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "ubus hisi vdm: " fmt + +#include "../../ubus.h" +#include "../../msg.h" +#include "../../pool.h" +#include "../../instance.h" +#include "../../ubus_entity.h" +#include "hisi-msg.h" +#include "hisi-ubus.h" +#include "vdm.h" + +struct opcode_func_map { + u16 sub_opcode; + u16 idev_pkt_size; + char print_info[16]; + u8 (*idev_handler)(struct ub_bus_controller *ubc, struct vdm_msg_pkt *pkt); + int idev_pld_size; +}; + +/** + * inner_ub_get_ent_by_guid - search for device by guid in the idev message + * + * @guid: guid in idev message + * @dev_list: &ubc->devs + */ +static struct ub_entity *inner_ub_get_ent_by_guid(guid_t *guid, + struct list_head *dev_list) +{ + struct ub_entity *uent; + + list_for_each_entry(uent, dev_list, node) + if (guid_equal(guid, &uent->guid.id)) + return uent; + + return NULL; +} + +/* + * ub_get_pue - search for physical device by guid and mue_idx + * + * First search for entity0 in ubc->dev_list by guid, + * then search for mue_idx in entity0->mue_list by mue_idx + */ +static struct ub_entity *ub_get_pue(guid_t *guid, u16 mue_idx, + struct list_head *dev_list) +{ + struct ub_entity *uent = inner_ub_get_ent_by_guid(guid, dev_list); + struct ub_entity *pue; + + if (!uent) { + pr_err("find uent with the guid failed\n"); + return NULL; + } + + if (is_primary(uent) && mue_idx == 0) { + ub_info(uent, + "This uent is primary dev, return uent directly\n"); + return uent; + } + + list_for_each_entry(pue, &uent->mue_list, node) { + ub_info(pue, "the pue num=%#x\n", pue->uent_num); + if (pue->entity_idx == mue_idx) + return pue; + } + + return NULL; +} + +static void ub_vdm_msg_rsp(struct ub_bus_controller *ubc, + struct vdm_msg_pkt *pkt, u8 status) +{ + struct msg_pkt_header *header = &pkt->header; + struct msg_info info = {}; + bool local; + u32 size; + int ret; + + size = (MSG_PKT_HEADER_SIZE + VENDOR_GUID_PLD_SIZE + + VDM_MSG_DW0_PLD_SIZE) << MSG_REQ_SIZE_OFFSET; + + local = ub_rsp_msg_init(header, status, + VENDOR_GUID_PLD_SIZE + VDM_MSG_DW0_PLD_SIZE); + + message_info_init(&info, local ? ubc->uent : NULL, pkt, NULL, size); + ret = message_response(ubc->mdev, &info, header->msgetah.code); + if (ret) + dev_err(&ubc->dev, "send vdm response message failed, ret=%d\n", ret); +} + +/** + * ub_idevice_enable_handle - physical/virtual device entity enable handle + * + * @pue: physical device + * @idx: index of device entity + * @is_mue: whether is physical device. 1 for yes, 0 for no + * @map: the mapping of virtual devices + * @alloc_dev: a temporary variable used to receive allocated dev and return the information + */ +static int ub_idevice_enable_handle(struct ub_entity *pue, u16 idx, u8 is_mue, + struct ue_map *map, + struct ub_entity **alloc_dev) +{ + struct ub_entity *dev, *uent; + int ret; + + list_for_each_entry(uent, &pue->mue_list, node) + if (uent->entity_idx == idx) + return -EEXIST; + + list_for_each_entry(uent, &pue->ue_list, node) + if (uent->entity_idx == idx) + return -EEXIST; + + dev = ub_alloc_ent(); + if (!dev) + return -ENOMEM; + + dev->pool = true; + dev->entity_idx = idx; + dev->pue = pue; + dev->dev.parent = &pue->dev; + dev->ubc = pue->ubc; + dev->cna = pue->cna; + dev->upi = pue->upi; + + if (is_mue) { + dev->is_mue = is_mue; + dev->uem.start_entity_idx = map->start_entity_idx; + dev->uem.end_entity_idx = map->end_entity_idx; + dev->total_ues = map->end_entity_idx - map->start_entity_idx + 1; + dev->is_vdm_idev = 1; + } + + ret = ub_setup_ent(dev); + if (ret < 0) { + kfree(dev); + return ret; + } + + ub_entity_get(pue); + ub_entity_add(dev, pue); + + *alloc_dev = dev; + + return 0; +} + +/** + * ub_idevice_pue_add_handler - add mue to the bus + * + * @ubc: ub bus controller + * @pkt: idev pue message packet, contains header and payload + */ +static u8 ub_idevice_pue_add_handler(struct ub_bus_controller *ubc, struct vdm_msg_pkt *pkt) +{ + struct idev_pue_reg_pld *pld = &pkt->pd_reg_pld; + struct ub_entity *pue, *alloc_dev = NULL; + struct ue_map map = {}; + char buf[SZ_64] = {}; + u8 status; + int ret; + + /* search corresponding device by guid in message payload */ + pue = inner_ub_get_ent_by_guid((guid_t *)pld->guid, &ubc->devs); + if (!pue) { + (void)ub_show_guid((struct ub_guid *)pld->guid, buf); + dev_err(&ubc->dev, "find uent by guid in pue reg func failed\n"); + status = UB_MSG_RSP_EXEC_ENODEV; + goto pue_reg_rsp; + } + + map.start_entity_idx = pld->start_ue_entity_idx; + map.end_entity_idx = pld->end_ue_entity_idx; + + if (!pld->ue_cnt) { + map.start_entity_idx = pue->entity_idx; + map.end_entity_idx = pue->entity_idx; + } else if (pld->ue_cnt != map.end_entity_idx - map.start_entity_idx + 1) { + dev_err(&ubc->dev, "Invalid ue cnt: [%u]\n" + "The ue cnt must be equal to end: [%d] - start: [%d] + 1\n", + pld->ue_cnt, map.end_entity_idx, map.start_entity_idx); + status = UB_MSG_RSP_EXEC_EINVAL; + goto pue_reg_rsp; + } + + if (!ub_bus_instance_exist(pld->user_eid[0])) { + dev_err(&ubc->dev, "pue add msg user eid[%#x] not exist\n", + pld->user_eid[0]); + status = UB_MSG_RSP_EXEC_EINVAL; + goto pue_reg_rsp; + } + + /* enable pue */ + ret = ub_idevice_enable_handle(pue, pld->pue_entity_idx, 1, &map, + &alloc_dev); + if (ret == 0) { + alloc_dev->user_eid = pld->user_eid[0]; + ub_info(pue, "enable idev pue succeeded, user_eid=0x%x\n", + pld->user_eid[0]); + status = UB_MSG_RSP_SUCCESS; + } else if (ret == -EEXIST) { + ub_err(pue, "The pue idx[%u] is already exist\n", + pld->pue_entity_idx); + status = UB_MSG_RSP_EXEC_EEXIST; + } else { + ub_err(pue, "enable idev pue failed\n"); + status = UB_MSG_RSP_EXEC_ENOEXEC; + } + +pue_reg_rsp: + ub_vdm_msg_rsp(ubc, pkt, status); + if (status == UB_MSG_RSP_SUCCESS) + ub_start_ent(alloc_dev); + + return status; +} + +/** + * ub_idevice_pue_rls_handler - send message to the FM to release the idev_pue + * + * @ubc: ub_bus_controller + * @pkt: message packet + */ +static u8 ub_idevice_pue_rls_handler(struct ub_bus_controller *ubc, struct vdm_msg_pkt *pkt) +{ + struct idev_pue_rls_pld *pld = &pkt->pd_rls_pld; + struct ub_entity *uent; + u8 status; + + /* search device by guid and pue entity idx in message payload */ + uent = ub_get_pue((guid_t *)pld->guid, pld->pue_entity_idx, &ubc->devs); + if (!uent) { + dev_err(&ubc->dev, "find pue failed, pue entity_idx in mue rls pkt=%u\n", + pld->pue_entity_idx); + status = UB_MSG_RSP_EXEC_ENODEV; + } else { + status = UB_MSG_RSP_SUCCESS; + } + + ub_vdm_msg_rsp(ubc, pkt, status); + + if (status == UB_MSG_RSP_SUCCESS) + ub_disable_ent(uent); + + return status; +} + +/* + * ub_idevice_ue_add_handler - add ue to the bus + */ +static u8 ub_idevice_ue_add_handler(struct ub_bus_controller *ubc, struct vdm_msg_pkt *pkt) +{ + struct idev_ue_reg_pld *pld = &pkt->vd_reg_pld; + struct ub_entity *pue, *alloc_dev = NULL; + u16 ue_entity_idx = pld->ue_entity_idx; + int start_idx, end_idx, ret; + int lock = 0; + u8 status; + + /* check whether pue is registered. */ + pue = ub_get_pue((guid_t *)pld->guid, pld->pue_entity_idx, &ubc->devs); + if (!pue) { + dev_err(&ubc->dev, "find pue failed, pue entity_idx in ue reg pkt=%u\n", + pld->pue_entity_idx); + status = UB_MSG_RSP_EXEC_ENODEV; + goto ue_reg_rsp; + } + + if (pue->is_vdm_idev) { + start_idx = pue->uem.start_entity_idx; + end_idx = pue->uem.end_entity_idx; + if (ue_entity_idx < start_idx || ue_entity_idx > end_idx) { + ub_err(pue, + "invalid ue entity_idx=%u, start_idx=%d, end_idx=%d\n", + ue_entity_idx, start_idx, end_idx); + status = UB_MSG_RSP_EXEC_EINVAL; + goto ue_reg_rsp; + } + } else { + ub_info(pue, + "The pue of this vdm ue to be enabled is normal\n"); + } + + if (!ub_bus_instance_exist(pld->user_eid[0])) { + dev_err(&ubc->dev, "ue add msg user eid[%#x] not exist\n", + pld->user_eid[0]); + status = UB_MSG_RSP_EXEC_EINVAL; + goto ue_reg_rsp; + } + + lock = device_trylock(&pue->dev); + if (!lock) { + status = UB_MSG_RSP_EXEC_EBUSY; + goto ue_reg_rsp; + } + + ret = ub_idevice_enable_handle(pue, ue_entity_idx, 0, NULL, &alloc_dev); + if (ret == 0) { + alloc_dev->user_eid = pld->user_eid[0]; + ub_info(pue, "enable idev ue succeeded, user_eid=0x%x\n", + pld->user_eid[0]); + status = UB_MSG_RSP_SUCCESS; + } else if (ret == -EEXIST) { + ub_err(pue, "The ue idx[%u] is already exist\n", + ue_entity_idx); + status = UB_MSG_RSP_EXEC_EEXIST; + } else { + ub_err(pue, "enable idev ue failed\n"); + status = UB_MSG_RSP_EXEC_ENOEXEC; + } + +ue_reg_rsp: + ub_vdm_msg_rsp(ubc, pkt, status); + if (status == UB_MSG_RSP_SUCCESS) { + ub_start_ent(alloc_dev); + pue->num_ues += 1; + } + + if (lock) + device_unlock(&pue->dev); + + return status; +} + +/** + * ub_idevice_ue_rls_handler - send message to the FM to release the idev_pue + * + * @ubc: ub_bus_controller + * @pkt: message packet + */ +static u8 ub_idevice_ue_rls_handler(struct ub_bus_controller *ubc, struct vdm_msg_pkt *pkt) +{ + struct idev_ue_rls_pld *pld = &pkt->vd_rls_pld; + struct ub_entity *pue, *vd_dev, *tmp; + u16 ue_entity_idx = pld->ue_entity_idx; + u16 start_idx, end_idx; + int lock = 0; + u8 status; + + /* search for pue with guid. Return an error if pue does not exist */ + pue = ub_get_pue((guid_t *)pld->guid, pld->pue_entity_idx, &ubc->devs); + if (!pue) { + dev_err(&ubc->dev, "find pue failed, pue entity_idx in ue rls pkt=%u\n", + pld->pue_entity_idx); + status = UB_MSG_RSP_EXEC_ENODEV; + goto ue_rls_rsp; + } + + if (pue->is_vdm_idev) { + start_idx = pue->uem.start_entity_idx; + end_idx = pue->uem.end_entity_idx; + if (ue_entity_idx < start_idx || ue_entity_idx > end_idx) { + ub_err(pue, + "invalid ue entity_idx=%u, start_idx=%u, end_idx=%u\n", + ue_entity_idx, start_idx, end_idx); + status = UB_MSG_RSP_EXEC_EINVAL; + goto ue_rls_rsp; + } + } else { + ub_info(pue, + "The pue of this vdm ue to be disabled is normal\n"); + } + + lock = device_trylock(&pue->dev); + if (!lock) { + status = UB_MSG_RSP_EXEC_EBUSY; + goto ue_rls_rsp; + } + + status = UB_MSG_RSP_EXEC_ENODEV; + /* otherwise, delete this ue with ue idx in message payload */ + list_for_each_entry_safe(vd_dev, tmp, &pue->ue_list, node) + if (ue_entity_idx == vd_dev->entity_idx) { + status = UB_MSG_RSP_SUCCESS; + break; + } + if (status == UB_MSG_RSP_EXEC_ENODEV) + ub_err(pue, "find vd_dev with entity_idx %u failed\n", ue_entity_idx); + +ue_rls_rsp: + ub_vdm_msg_rsp(ubc, pkt, status); + if (status == UB_MSG_RSP_SUCCESS) { + ub_disable_ent(vd_dev); + pue->num_ues -= 1; + } + + if (lock) + device_unlock(&pue->dev); + + return status; +} + +static u8 ub_vdm_create_bi_bypass_ummu(struct ub_bus_controller *ubc, struct vdm_msg_pkt *pkt) +{ + struct create_bi_bypass_pld *pld = &pkt->bi_bypass_pld; + struct msg_pkt_header *header = &pkt->header; + u8 status = UB_MSG_RSP_SUCCESS; + struct msg_info q_info = {}; + enum eid_type type; + bool local; + u32 size; + int ret; + + if (pld->m) + type = EID_BYPASS; + else + type = EID_NONE; + + ret = ub_msg_bus_instance_create(ubc, pld->guid, pld->eid[0], pld->upi, + type); + if (ret) { + dev_err(&ubc->dev, "bi create msg failed ret[%d]\n", ret); + status = UB_MSG_RSP_EXEC_ENOEXEC; + } + + size = (MSG_PKT_HEADER_SIZE + VENDOR_GUID_PLD_SIZE + + VDM_MSG_DW0_PLD_SIZE) << MSG_REQ_SIZE_OFFSET; + local = ub_rsp_msg_init(header, status, + VENDOR_GUID_PLD_SIZE + VDM_MSG_DW0_PLD_SIZE); + message_info_init(&q_info, local ? ubc->uent : NULL, pkt, NULL, size); + ret = message_response(ubc->mdev, &q_info, header->msgetah.code); + if (ret) { + dev_err(&ubc->dev, "create bi bypass rsp msg failed, ret=%d\n", ret); + status = UB_MSG_RSP_EXEC_ENOEXEC; + } + + return status; +} + +struct opcode_func_map idev_func_mapping[] = { + { VDM_SUB_OPCODE_MUE_REG, MSG_IDEV_MUE_REG_SIZE, "MUE register", + ub_idevice_pue_add_handler, IDEV_MUE_REG_PLD_TOTAL_SIZE }, + { VDM_SUB_OPCODE_MUE_RLS, MSG_IDEV_MUE_RLS_SIZE, "MUE release", + ub_idevice_pue_rls_handler, IDEV_MUE_RLS_PLD_TOTAL_SIZE }, + { VDM_SUB_OPCODE_UE_REG, MSG_IDEV_UE_REG_SIZE, "UE register", + ub_idevice_ue_add_handler, IDEV_UE_REG_PLD_TOTAL_SIZE }, + { VDM_SUB_OPCODE_UE_RLS, MSG_IDEV_UE_RLS_SIZE, "UE release", + ub_idevice_ue_rls_handler, IDEV_UE_RLS_PLD_TOTAL_SIZE }, + { VDM_BI_CREATE_BYPASS_UMMU, VDM_BI_CREATE_BYPASS_SIZE, "Vdm bi create", + ub_vdm_create_bi_bypass_ummu, VDM_BI_CREATE_BYPASS_UMMU_PLD_SIZE }, +}; + +static int ub_vdm_msg_info_handle(struct ub_bus_controller *ubc, + struct vdm_msg_pkt *pkt, u16 len, + u16 sub_opcode) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(idev_func_mapping); idx++) { + if (sub_opcode == idev_func_mapping[idx].sub_opcode) { + if (len < idev_func_mapping[idx].idev_pkt_size) { + dev_err(&ubc->dev, + "Message length[%#x] is wrong\n", len); + return UB_MSG_RSP_EXEC_EINVAL; + } + (void)idev_func_mapping[idx].idev_handler(ubc, pkt); + return UB_MSG_RSP_SUCCESS; + } + } + + dev_err(&ubc->dev, "vdm sub opcode is invalid, sub_opcode = %#x\n", sub_opcode); + return UB_MSG_RSP_EXEC_EINVAL; +} + +static int hi_vdm_vendor_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct vdm_msg_pkt *vdm_pkt = (struct vdm_msg_pkt *)pkt; + struct msg_pkt_dw0 *pld_dw0 = &vdm_pkt->pld_dw0; + u16 sub_opcode; + u8 opcode; + + if (len < (MSG_PKT_HEADER_SIZE + VENDOR_GUID_PLD_SIZE + + VDM_MSG_DW0_PLD_SIZE)) { + dev_err(&ubc->dev, "vdm msg len[%#x] is wrong\n", len); + return UB_MSG_RSP_EXEC_EINVAL; + } + + opcode = pld_dw0->opcode; + sub_opcode = pld_dw0->sub_opcode; + switch (opcode) { + case VDM_OPCODE_FM2UB_COMM_MSG: + return ub_vdm_msg_info_handle(ubc, vdm_pkt, len, sub_opcode); + default: + dev_err(&ubc->dev, "vdm opcode type [%u] not support\n", + opcode); + } + + return UB_MSG_RSP_EXEC_EINVAL; +} + +void hi_vdm_rx_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len) +{ + struct msg_pkt_header *header = (struct msg_pkt_header *)pkt; + u8 sub_msg_code = header->msgetah.sub_msg_code; + u8 status; + + switch (sub_msg_code) { + case UB_VENDOR_MSG: + status = hi_vdm_vendor_handler(ubc, pkt, len); + break; + default: + dev_err(&ubc->dev, "vdm sub msg code[%#x] not support\n", + sub_msg_code); + status = UB_MSG_RSP_EXEC_EINVAL; + } + + if (status) + ub_vdm_msg_rsp(ubc, (struct vdm_msg_pkt *)pkt, status); +} + +static struct ub_entity *ub_get_primary_ent(struct ub_entity *uent) +{ + if (is_primary(uent)) + return uent; + + return uent->is_mue ? uent->pue : uent->pue->pue; +} + +/* + * hi_send_entity_enable_msg - send entity enable message + */ +int hi_send_entity_enable_msg(struct ub_entity *uent, u8 enable) +{ + struct entity_enable_pld *enable_pld; + struct vdm_msg_pkt pkt = {}; + struct msg_info info = {}; + struct msg_pkt_dw0 *pld_dw0; + struct ub_entity *pue; + u8 status; + int ret; + + if (!uent->ubc->cluster) + return 0; + + ub_msg_pkt_header_init(&pkt.header, uent, ENTITY_ENABLE_PLD_SIZE, + code_gen(UB_MSG_CODE_VDM, UB_VENDOR_MSG, + MSG_REQ), true); + + pkt.guid_high = *(u64 *)(&uent->ubc->uent->guid.dw[SZ_2]); + pld_dw0 = &pkt.pld_dw0; + pld_dw0->opcode = VDM_OPCODE_UB2FM_COMM_MSG; + pld_dw0->sub_opcode = VDM_SUB_OPCODE_ENTITY_ENABLE; + enable_pld = &pkt.enable_pld; + enable_pld->enable = enable; + if (is_device(uent)) { + enable_pld->entity_type = ENTITY_TYPE_DEV; + enable_pld->eid[0] = uent->eid; + } else { /* idev */ + enable_pld->entity_type = ENTITY_TYPE_IDEV; + pue = ub_get_primary_ent(uent); + memcpy(enable_pld->guid, pue->guid.dw, + sizeof(u32) * UB_GUID_DW_NUM); + enable_pld->entity_idx = uent->entity_idx; + } + + message_info_init(&info, uent->ubc->uent, &pkt, &pkt, + (ENTITY_ENABLE_MSG_PKT_SIZE << MSG_REQ_SIZE_OFFSET) | + ENTITY_ENABLE_MSG_PKT_SIZE); + + ub_info(uent, "Sync request entity enable msg, enable=%u\n", enable); + + ret = hi_message_sync_request_sched(uent->message->mdev, &info, + pkt.header.msgetah.code); + if (ret) { + ub_err(uent, "msg sync request ret=%d\n", ret); + return ret; + } + + status = pkt.header.msgetah.rsp_status; + if (status != UB_MSG_RSP_SUCCESS) { + ub_err(uent, "msg rsp status=%#02x\n", status); + return -EINVAL; + } + + return 0; +} + +int hi_send_port_reset_msg(struct ub_entity *uent, u16 port_idx) +{ + struct port_reset_pld *rst_pld; + struct vdm_msg_pkt pkt = {}; + struct msg_info info = {}; + struct msg_pkt_dw0 *pld_dw0; + u8 status; + int ret; + + if (!uent->ubc->cluster) + return 0; + + ub_msg_pkt_header_init(&pkt.header, uent, VDM_PORT_RESET_PLD_SIZE, + code_gen(UB_MSG_CODE_VDM, UB_VENDOR_MSG, + MSG_REQ), true); + + pkt.guid_high = *(u64 *)(&uent->ubc->uent->guid.dw[SZ_2]); + pld_dw0 = &pkt.pld_dw0; + pld_dw0->opcode = VDM_OPCODE_UB2FM_COMM_MSG; + pld_dw0->sub_opcode = VDM_SUB_OPCODE_PORT_RESET; + rst_pld = &pkt.reset_pld; + rst_pld->port_idx = port_idx; + + message_info_init(&info, uent->ubc->uent, &pkt, &pkt, + (VDM_PORT_RESET_SIZE << MSG_REQ_SIZE_OFFSET) | + VDM_PORT_RESET_SIZE); + + ub_info(uent, "Sync request port reset msg\n"); + + ret = hi_message_sync_request(uent->message->mdev, &info, + pkt.header.msgetah.code); + if (ret) { + ub_err(uent, "msg sync request ret=%d\n", ret); + return ret; + } + + status = pkt.header.msgetah.rsp_status; + if (status != UB_MSG_RSP_SUCCESS) { + ub_err(uent, "msg rsp status=%#02x\n", status); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/ub/ubus/vendor/hisilicon/vdm.h b/drivers/ub/ubus/vendor/hisilicon/vdm.h new file mode 100644 index 0000000000000000000000000000000000000000..8b3b4e4f616f588b41361af0d40e3e54f12b3543 --- /dev/null +++ b/drivers/ub/ubus/vendor/hisilicon/vdm.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef __VDM_H__ +#define __VDM_H__ + +/* VDM message opcode */ +enum vdm_opcode { + VDM_OPCODE_FM2UB_COMM_MSG = 0, /* FM To UB node Communication Message */ + VDM_OPCODE_UB2FM_COMM_MSG = 0x1, /* UB node To FM Communication Message */ + VDM_OPCODE_UB2UB_COMM_MSG = 0x2, /* UB node To UB node Communication Message */ + VDM_OPCODE_FW_COMM_MSG = 0x3, /* Firmware Communication Message */ +}; + +enum vdm_fm2ub_sub_opcode { + VDM_SUB_OPCODE_MUE_REG = 0x1, + VDM_SUB_OPCODE_MUE_RLS = 0x2, + VDM_SUB_OPCODE_UE_REG = 0x3, + VDM_SUB_OPCODE_UE_RLS = 0x4, + VDM_BI_CREATE_BYPASS_UMMU = 0x5, +}; + +enum vdm_ub2fm_sub_opcode { + VDM_SUB_OPCODE_ENTITY_ENABLE = 0x1, + VDM_SUB_OPCODE_PORT_RESET = 0x2, +}; + +struct msg_pkt_dw0 { + /* DW0 */ + u32 rsvd0 : 16; + u32 opcode : 6; + u32 sub_opcode : 10; +}; +#define VDM_MSG_DW0_PLD_SIZE 4 + +/* send entity enable msg payload */ +struct entity_enable_pld { + /* DW1 */ + u32 entity_type : 1; + u32 enable : 1; + u32 rsvd1 : 30; + /* DW2~DW5 */ + u32 eid[4]; + /* DW6~DW9 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW10 */ + u16 entity_idx; + u16 rsvd2; +}; +#define ENTITY_ENABLE_PLD_SIZE 52 + +#define ENTITY_ENABLE_MSG_PKT_SIZE (MSG_PKT_HEADER_SIZE + ENTITY_ENABLE_PLD_SIZE) + +enum entity_type { + ENTITY_TYPE_IDEV = 0, + ENTITY_TYPE_DEV = 1, +}; + +struct idev_pue_reg_pld { + /* DW1~DW4 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW5 */ + u16 pue_entity_idx; + u16 ue_cnt; + /* DW6 */ + u16 start_ue_entity_idx; + u16 end_ue_entity_idx; + /* DW7~DW10 */ + u32 user_eid[4]; +}; +#define IDEV_MUE_REG_PLD_TOTAL_SIZE 52 + +struct idev_pue_rls_pld { + /* DW1~DW4 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW5 */ + u16 pue_entity_idx; + u16 rls_reason; +}; +#define IDEV_MUE_RLS_PLD_TOTAL_SIZE 32 + +struct idev_ue_reg_pld { + /* DW1~DW4 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW5 */ + u16 pue_entity_idx; + u16 ue_entity_idx; + /* DW6~DW9 */ + u32 user_eid[4]; +}; +#define IDEV_UE_REG_PLD_TOTAL_SIZE 48 + +struct idev_ue_rls_pld { + /* DW1~DW4 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW5 */ + u16 pue_entity_idx; + u16 ue_entity_idx; + /* DW6 */ + u16 rls_reason; + u16 rsvd1; +}; +#define IDEV_UE_RLS_PLD_TOTAL_SIZE 36 + +struct create_bi_bypass_pld { + /* DW1~DW4 */ + u32 guid[UB_GUID_DW_NUM]; + /* DW5~DW8 */ + u32 eid[4]; + /* DW9 */ + u32 upi : 15; + u32 m : 1; + u32 rsvd2 : 16; +}; +#define VDM_BI_CREATE_BYPASS_UMMU_PLD_SIZE 48 + +struct port_reset_pld { + /* DW1 */ + u16 rsvd; + u16 port_idx; +}; +#define VDM_PORT_RESET_PLD_SIZE 16 + +#define MSG_IDEV_MUE_REG_SIZE \ + (MSG_PKT_HEADER_SIZE + IDEV_MUE_REG_PLD_TOTAL_SIZE) +#define MSG_IDEV_MUE_RLS_SIZE \ + (MSG_PKT_HEADER_SIZE + IDEV_MUE_RLS_PLD_TOTAL_SIZE) +#define MSG_IDEV_UE_REG_SIZE \ + (MSG_PKT_HEADER_SIZE + IDEV_UE_REG_PLD_TOTAL_SIZE) +#define MSG_IDEV_UE_RLS_SIZE \ + (MSG_PKT_HEADER_SIZE + IDEV_UE_RLS_PLD_TOTAL_SIZE) +#define VDM_BI_CREATE_BYPASS_SIZE \ + (MSG_PKT_HEADER_SIZE + VDM_BI_CREATE_BYPASS_UMMU_PLD_SIZE) +#define VDM_PORT_RESET_SIZE \ + (MSG_PKT_HEADER_SIZE + VDM_PORT_RESET_PLD_SIZE) + +#define VENDOR_GUID_PLD_SIZE 8 + +struct vdm_msg_pkt { + struct msg_pkt_header header; + u64 guid_high; + struct msg_pkt_dw0 pld_dw0; + union { + struct port_reset_pld reset_pld; + struct entity_enable_pld enable_pld; + struct idev_pue_reg_pld pd_reg_pld; + struct idev_pue_rls_pld pd_rls_pld; + struct idev_ue_reg_pld vd_reg_pld; + struct idev_ue_rls_pld vd_rls_pld; + struct create_bi_bypass_pld bi_bypass_pld; + }; +}; + +void hi_vdm_rx_msg_handler(struct ub_bus_controller *ubc, void *pkt, u16 len); + +#endif /* __VDM_H__ */ diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig index d9984ee13c9330c3777a73d79232043f53ac3b2a..eb8f1756fd8367f8cb5b8331dccb5502de71232d 100644 --- a/drivers/vfio/Kconfig +++ b/drivers/vfio/Kconfig @@ -91,6 +91,7 @@ config VFIO_DEBUGFS entries as required. source "drivers/vfio/pci/Kconfig" +source "drivers/vfio/ubus/Kconfig" source "drivers/vfio/platform/Kconfig" source "drivers/vfio/mdev/Kconfig" source "drivers/vfio/fsl-mc/Kconfig" diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile index b2fc9fb499d8690cf7d75e32bdf9bbb02efdf9f7..b875e7f26e315bd05dde2af35c86cfa80f8b2584 100644 --- a/drivers/vfio/Makefile +++ b/drivers/vfio/Makefile @@ -12,6 +12,7 @@ vfio-$(CONFIG_VFIO_DEBUGFS) += debugfs.o obj-$(CONFIG_VFIO_IOMMU_TYPE1) += vfio_iommu_type1.o obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_spapr_tce.o obj-$(CONFIG_VFIO_PCI_CORE) += pci/ +obj-$(CONFIG_VFIO_UB) += ubus/ obj-$(CONFIG_VFIO_PLATFORM_BASE) += platform/ obj-$(CONFIG_VFIO_MDEV) += mdev/ obj-$(CONFIG_VFIO_FSL_MC) += fsl-mc/ diff --git a/drivers/vfio/ubus/Kconfig b/drivers/vfio/ubus/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..130d11e0c21af8d9198fec8aec708096ede9fa5f --- /dev/null +++ b/drivers/vfio/ubus/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0+ +menu "VFIO support for UBUS devices" + depends on UB_UBUS && MMU + +config VFIO_UB + tristate "Generic VFIO support for any UBUS device" + select VFIO_VIRQFD + select IRQ_BYPASS_MANAGER + help + Support for the generic UBUS VFIO bus driver which can connect any + UBUS device to the VFIO framework. + + If you don't know what to do here, say N. + +endmenu diff --git a/drivers/vfio/ubus/Makefile b/drivers/vfio/ubus/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e575a11b39b4fc51e0614be3084bdcc5fdd731a1 --- /dev/null +++ b/drivers/vfio/ubus/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0+ + +vfio-ub-y := vfio_ub.o vfio_ub_core.o vfio_ub_config.o vfio_ub_rdwr.o vfio_ub_intrs.o +obj-$(CONFIG_VFIO_UB) += vfio-ub.o diff --git a/drivers/vfio/ubus/vfio_ub.c b/drivers/vfio/ubus/vfio_ub.c new file mode 100644 index 0000000000000000000000000000000000000000..8e89cadc18479bc262e8171e01d6709608bd3e7d --- /dev/null +++ b/drivers/vfio/ubus/vfio_ub.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + * + * Thanks to Alex Williamson and Tom Lyon for their original + * vfio implementation. + * + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "vfio ub: " fmt + +#include +#include +#include + +#include "vfio_ub_private.h" + +static const struct vfio_device_ops vfio_ub_ops = { + .name = "vfio-ub", + .init = vfio_ub_core_init_dev, + .release = vfio_ub_core_release_dev, + .open_device = vfio_ub_core_open_device, + .close_device = vfio_ub_core_close_device, + .read = vfio_ub_core_read, + .write = vfio_ub_core_write, + .ioctl = vfio_ub_core_ioctl, + .mmap = vfio_ub_core_mmap, + .request = vfio_ub_core_request, + .bind_iommufd = vfio_iommufd_physical_bind, + .unbind_iommufd = vfio_ub_iommufd_physical_unbind, + .attach_ioas = vfio_ub_iommufd_physical_attach_ioas, + .detach_ioas = vfio_ub_iommufd_physical_detach_ioas, +}; + +static int vfio_ub_probe(struct ub_entity *uent, const struct ub_device_id *id) +{ + struct vfio_ub_core_device *vdev; + int ret; + + vdev = vfio_alloc_device(vfio_ub_core_device, vdev, &uent->dev, &vfio_ub_ops); + if (IS_ERR(vdev)) + return PTR_ERR(vdev); + + dev_set_drvdata(&uent->dev, vdev); + ret = vfio_ub_core_register_device(vdev); + if (ret) + goto out_put_vdev; + + ub_dbg(uent, "vfio-ub driver has matched\n"); + return 0; + +out_put_vdev: + dev_set_drvdata(&uent->dev, NULL); + vfio_put_device(&vdev->vdev); + return ret; +} + +static void vfio_ub_remove(struct ub_entity *uent) +{ + struct vfio_ub_core_device *vdev = + (struct vfio_ub_core_device *)dev_get_drvdata(&uent->dev); + + vfio_ub_core_disable_all(vdev); + vfio_ub_core_unregister_device(vdev); + dev_set_drvdata(&uent->dev, NULL); + vfio_put_device(&vdev->vdev); + + ub_dbg(uent, "vfio-ub driver has removed\n"); +} + +static const struct ub_device_id vfio_ub_table[] = { + { UB_DRIVER_OVERRIDE_ENTITY_VFIO(UB_ANY_ID, UB_ANY_ID) }, /* match all by default */ + {} +}; +MODULE_DEVICE_TABLE(ub, vfio_ub_table); + +static struct ub_driver vfio_ub_driver = { + .name = "vfio-ub", + .id_table = vfio_ub_table, + .probe = vfio_ub_probe, + .remove = vfio_ub_remove, + .driver_managed_dma = true, +}; + +static int __init vfio_ub_init(void) +{ + int ret; + + ret = vfio_ub_init_perm_bits(); + if (ret) + return ret; + + ret = ub_register_driver(&vfio_ub_driver); + if (ret) + goto out_driver; + + return 0; + +out_driver: + vfio_ub_uninit_perm_bits(); + return ret; +} + +static void __exit vfio_ub_exit(void) +{ + ub_unregister_driver(&vfio_ub_driver); + vfio_ub_uninit_perm_bits(); +} + +module_init(vfio_ub_init); +module_exit(vfio_ub_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VFIO UB - User Level meta-driver"); +MODULE_IMPORT_NS(UB_UBUS); diff --git a/drivers/vfio/ubus/vfio_ub_config.c b/drivers/vfio/ubus/vfio_ub_config.c new file mode 100644 index 0000000000000000000000000000000000000000..9b204d6300ad260857045741da5fbc3a74e317d3 --- /dev/null +++ b/drivers/vfio/ubus/vfio_ub_config.c @@ -0,0 +1,1424 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + * + * Thanks to Alex Williamson and Tom Lyon for their original + * vfio implementation. + * + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "vfio ub: " fmt + +#include +#include +#include +#include + +#include "vfio_ub_private.h" + +#define NO_VIRT 0 +#define ALL_VIRT 0xFFFFFFFFU +#define NO_WRITE 0 +#define ALL_WRITE 0xFFFFFFFFU +#define NO_ENTITY0_EO 0 +#define ENTITY0_EO 0xFFFFFFFFU +#define NO_ENTITYN_RO 0 +#define ENTITYN_RO 0xFFFFFFFFU + +/* Now, this attr control just support at byte granularity */ +enum { + FUNC_EXIST = 0x00000000, /* idev and ub device have this register */ + IDEV_NO_EXIST = 0x01010101, /* idev do not has this register */ + UDEV_NO_EXIST = 0x02020202, /* ub device do not has have this register */ + FUNC_NO_EXIST = 0x03030303, /* idev and ub device do not have this register */ +}; + +struct attrs { + __le32 ent0eo; + __le32 entnro; + __le32 exist; +}; + +#define CFG_MAP_INVALID (-1) + +#define UB_PORT_CAP_GAP 0x100 +#define UB_PORT0_BASIC_CAP 0x200 + +#define UB_CFG_POS_TO_CAP(pos) ((u64)(pos) >> 10) +#define UB_CFG_CAP_TO_POS(cap) ((u32)(cap) << 10) + +static const u8 zero_array[UB_SLICE_SZ] = {}; + +/* Handle endian-ness - ub and tables are little-endian */ +static inline void p_setb(struct perm_bits *p, int off, u8 virt, u8 write) +{ + p->virt[off] = virt; + p->write[off] = write; +} + +/* Handle endian-ness - ub and tables are little-endian */ +static inline void p_setw(struct perm_bits *p, int off, u16 virt, u16 write) +{ + *(__le16 *)(&p->virt[off]) = cpu_to_le16(virt); + *(__le16 *)(&p->write[off]) = cpu_to_le16(write); +} + +/* Handle endian-ness - ub and tables are little-endian */ +static inline void p_setd(struct perm_bits *p, int off, u32 virt, u32 write) +{ + *(__le32 *)(&p->virt[off]) = cpu_to_le32(virt); + *(__le32 *)(&p->write[off]) = cpu_to_le32(write); +} + +static inline void p_attr_setb(struct perm_bits *p, int off, u8 ent0eo, u8 entnro, u8 exist) +{ + p->ent0eo[off] = ent0eo; + p->entnro[off] = entnro; + p->exist[off] = exist; +} + +static inline void p_attr_setw(struct perm_bits *p, int off, u16 ent0eo, u16 entnro, u16 exist) +{ + *(__le16 *)(&p->ent0eo[off]) = cpu_to_le16(ent0eo); + *(__le16 *)(&p->entnro[off]) = cpu_to_le16(entnro); + *(__le16 *)(&p->exist[off]) = cpu_to_le16(exist); +} + +static inline void p_attr_setd(struct perm_bits *p, int off, u32 ent0eo, u32 entnro, u32 exist) +{ + *(__le32 *)(&p->ent0eo[off]) = cpu_to_le32(ent0eo); + *(__le32 *)(&p->entnro[off]) = cpu_to_le32(entnro); + *(__le32 *)(&p->exist[off]) = cpu_to_le32(exist); +} + +static int vfio_ub_do_user_config_read(struct ub_entity *uent, u64 pos, + __le32 *val, int count) +{ + int ret = -EINVAL; + u32 tmp_val = 0; + u8 tmp_byte; + u16 tmp_word; + + switch (count) { + case BYTE_SIZE: + ret = ub_cfg_read_byte(uent, pos, &tmp_byte); + tmp_val = tmp_byte; + break; + case WORD_SIZE: + ret = ub_cfg_read_word(uent, pos, &tmp_word); + tmp_val = tmp_word; + break; + case DWORD_SIZE: + ret = ub_cfg_read_dword(uent, pos, &tmp_val); + break; + default: + break; + } + + *val = cpu_to_le32(tmp_val); + + return ret; +} + +static bool vfio_ub_judge_type(struct ub_entity *uent, u8 exist) +{ + return ((((exist & IDEV_NO_EXIST) == 0) && (uent_type(uent) == UB_TYPE_ICONTROLLER && + uent_base_code(uent) != UB_BASE_CODE_BUS_CONTROLLER)) || + (((exist & UDEV_NO_EXIST) == 0) && (uent_type(uent) == UB_TYPE_CONTROLLER && + uent_base_code(uent) != UB_BASE_CODE_BUS_CONTROLLER))); +} + +static bool vfio_ub_judge_reg_read(struct ub_entity *uent, u8 ent0eo, u8 exist) +{ + if (vfio_ub_judge_type(uent, exist)) { + if (((ent0eo == (u8)ENTITY0_EO) && (uent->entity_idx == 0)) || + (ent0eo == NO_ENTITY0_EO)) { + return true; + } + } + + return false; +} + +static bool vfio_ub_judge_reg_write(struct ub_entity *uent, u8 ent0eo, u8 exist, u8 entnro) +{ + if (vfio_ub_judge_type(uent, exist)) { + if (((uent->entity_idx != 0) && (ent0eo == NO_ENTITY0_EO) && + (entnro == NO_ENTITYN_RO)) || uent->entity_idx == 0) { + return true; + } + } + + return false; +} + +static int vfio_ub_get_split_cfg_val(struct ub_entity *uent, u64 pos, + struct attrs *attr, __le32 *val, u32 count) +{ + u32 tmp_val = 0; + u8 byte; + u8 ent0eo_byte; + u8 exist_byte; + int ret; + + for (u32 i = 0; i < count; i++) { + ent0eo_byte = (attr->ent0eo >> i * BITS_PER_BYTE) & 0xff; + exist_byte = (attr->exist >> i * BITS_PER_BYTE) & 0xff; + if (vfio_ub_judge_reg_read(uent, ent0eo_byte, exist_byte)) { + ret = ub_cfg_read_byte(uent, pos + i, &byte); + if (!ret) + tmp_val = tmp_val | (((u32)byte) << i * BITS_PER_BYTE); + else + return ret; + } + } + + *val = tmp_val; + + return 0; +} + +static int vfio_ub_set_split_cfg_val(struct ub_entity *uent, u64 pos, + struct attrs *attr, __le32 val, u32 count) +{ + u8 byte; + u8 ent0eo_byte; + u8 exist_byte; + u8 entnro_byte; + int ret; + + for (int i = 0; i < count; i++) { + ent0eo_byte = (attr->ent0eo >> i * BITS_PER_BYTE) & 0xff; + exist_byte = (attr->exist >> i * BITS_PER_BYTE) & 0xff; + entnro_byte = (attr->entnro >> i * BITS_PER_BYTE) & 0xff; + if (vfio_ub_judge_reg_write(uent, ent0eo_byte, exist_byte, + entnro_byte)) { + byte = val >> i * BITS_PER_BYTE; + ret = ub_cfg_write_byte(uent, pos + i, byte); + if (ret) + return ret; + } + } + + return 0; +} + +static int vfio_ub_default_config_read(struct vfio_ub_core_device *vdev, + u64 pos, int count, __le32 *val); + +static struct perm_bits cap_perms[UB_CFG1_MAX_CAP + 1] = {}; + +static struct perm_bits port_basic_perms = {.readfn = vfio_ub_default_config_read }; + +/* when call this function, cap_id is always exist */ +static struct perm_bits *vfio_ub_find_cap_perm(u32 cap_id) +{ + if (cap_id <= UB_CFG1_MAX_CAP) + return &cap_perms[cap_id]; + + return &port_basic_perms; +} + +static int vfio_ub_user_config_read(struct ub_entity *uent, u64 pos, + __le32 *val, int count) +{ + struct perm_bits *perm; + struct attrs attr = {}; + u32 offset; + u32 cap_id; + + cap_id = UB_CFG_POS_TO_CAP(pos); + perm = vfio_ub_find_cap_perm(cap_id); + offset = pos - UB_CFG_CAP_TO_POS(cap_id); + + memcpy(&attr.ent0eo, perm->ent0eo + offset, count); + memcpy(&attr.exist, perm->exist + offset, count); + + /* quick processing for most situation */ + if (likely(attr.exist == FUNC_EXIST && attr.ent0eo == NO_ENTITY0_EO)) + return vfio_ub_do_user_config_read(uent, pos, val, count); + + /* read cfg at byte unit */ + return vfio_ub_get_split_cfg_val(uent, pos, &attr, val, count); +} + +static int vfio_ub_do_user_config_write(struct ub_entity *uent, u64 pos, + __le32 val, int count) +{ + int ret = -EINVAL; + u32 tmp_val = le32_to_cpu(val); + + switch (count) { + case BYTE_SIZE: + ret = ub_cfg_write_byte(uent, pos, tmp_val); + break; + case WORD_SIZE: + ret = ub_cfg_write_word(uent, pos, tmp_val); + break; + case DWORD_SIZE: + ret = ub_cfg_write_dword(uent, pos, tmp_val); + break; + default: + break; + } + + return ret; +} + +static int vfio_ub_user_config_write(struct ub_entity *uent, u64 pos, + __le32 val, int count) +{ + struct perm_bits *perm; + struct attrs attr = {}; + u32 offset; + u32 cap_id; + + cap_id = UB_CFG_POS_TO_CAP(pos); + perm = vfio_ub_find_cap_perm(cap_id); + offset = pos - UB_CFG_CAP_TO_POS(cap_id); + + memcpy(&attr.ent0eo, perm->ent0eo + offset, count); + memcpy(&attr.exist, perm->exist + offset, count); + memcpy(&attr.entnro, perm->entnro + offset, count); + + /* quick processing for most situation */ + if (likely(attr.exist == FUNC_EXIST && attr.ent0eo == NO_ENTITY0_EO + && attr.entnro == NO_ENTITYN_RO)) + return vfio_ub_do_user_config_write(uent, pos, val, count); + + return vfio_ub_set_split_cfg_val(uent, pos, &attr, val, count); +} + +static u8 *vfio_ub_find_cfg_buf(struct vfio_ub_core_device *vdev, u32 cap_id) +{ + int i; + int num; + + if (cap_id <= UB_CFG1_MAX_CAP) { + num = vdev->vconfig.map[cap_id]; + if (num != CFG_MAP_INVALID) + return vdev->vconfig.slice[num].cfg; + } else { + for (i = 0; i < vdev->vconfig.nums; i++) { + if (vdev->vconfig.slice[i].cap_id == cap_id) + return vdev->vconfig.slice[i].cfg; + } + } + + return NULL; +} + +static int vfio_ub_default_config_read(struct vfio_ub_core_device *vdev, + u64 pos, int count, __le32 *val) +{ + struct perm_bits *perm; + __le32 virt = 0; + u8 *buf; + u32 cap_id; + u32 offset; + + cap_id = UB_CFG_POS_TO_CAP(pos); + offset = pos - UB_CFG_CAP_TO_POS(cap_id); + perm = vfio_ub_find_cap_perm(cap_id); + buf = vfio_ub_find_cfg_buf(vdev, cap_id); + if (buf == NULL) + return -EINVAL; + + memcpy(val, buf + offset, count); + memcpy(&virt, perm->virt + offset, count); + + if (cpu_to_le32(~0U >> (DWORD_BITS - (count * BYTE_BITS))) != virt) { + struct ub_entity *uent = vdev->uent; + __le32 phys_val = 0; + int ret; + + ret = vfio_ub_user_config_read(uent, pos, &phys_val, count); + if (ret) + return ret; + + *val = (phys_val & ~virt) | (*val & virt); + } + + return count; +} + +static __le32 vfio_ub_get_virt_write_mask(struct ub_entity *uent, u64 pos, int count) +{ + u8 ent0eo_byte, exist_byte, entnro_byte; + struct perm_bits *perm; + __le32 tmp_val = 0; + u32 cap_id; + u32 offset; + + cap_id = UB_CFG_POS_TO_CAP(pos); + offset = pos - UB_CFG_CAP_TO_POS(cap_id); + perm = vfio_ub_find_cap_perm(cap_id); + + for (int i = 0; i < count; i++) { + /* register attrs control at byte unit */ + ent0eo_byte = perm->ent0eo[offset + i]; + exist_byte = perm->exist[offset + i]; + entnro_byte = perm->entnro[offset + i]; + + if (vfio_ub_judge_reg_write(uent, ent0eo_byte, exist_byte, + entnro_byte)) + tmp_val |= 0xff << (i * BITS_PER_BYTE); + } + + return tmp_val; +} + +static int vfio_ub_default_config_write(struct vfio_ub_core_device *vdev, + u64 pos, int count, __le32 val) +{ + __le32 virt = 0, write = 0, mask = 0; + struct perm_bits *perm; + u8 *buf; + u32 cap_id; + u32 offset; + + cap_id = UB_CFG_POS_TO_CAP(pos); + offset = pos - UB_CFG_CAP_TO_POS(cap_id); + perm = vfio_ub_find_cap_perm(cap_id); + buf = vfio_ub_find_cfg_buf(vdev, cap_id); + if (buf == NULL) + return -EINVAL; + + memcpy(&write, perm->write + offset, count); + + if (!write) + return count; /* just drop it, no writable bits */ + + memcpy(&virt, perm->virt + offset, count); + + if (write & virt) { + __le32 virt_val = 0; + + mask = vfio_ub_get_virt_write_mask(vdev->uent, pos, count); + memcpy(&virt_val, buf + offset, count); + virt_val &= ~(write & virt & mask); + virt_val |= (val & (write & virt & mask)); + + memcpy(buf + offset, &virt_val, count); + } + + /* Non-virtualzed and writable bits go to hardware */ + if (write & ~virt) { + struct ub_entity *uent = vdev->uent; + __le32 phys_val = 0; + int ret; + + ret = vfio_ub_user_config_read(uent, pos, &phys_val, count); + if (ret) + return ret; + + phys_val &= ~(write & ~virt); + phys_val |= (val & (write & ~virt)); + + ret = vfio_ub_user_config_write(uent, pos, phys_val, count); + if (ret) + return ret; + } + + return count; +} + +static void free_perm_bits(struct perm_bits *perm) +{ + vfree(perm->exist); + vfree(perm->entnro); + vfree(perm->ent0eo); + vfree(perm->write); + vfree(perm->virt); + perm->exist = NULL; + perm->entnro = NULL; + perm->ent0eo = NULL; + perm->virt = NULL; + perm->write = NULL; +} + +static int alloc_perm_bits(struct perm_bits *perm, int size) +{ + int tmp; + + tmp = round_up(size, DWORD_SIZE); + + /* + * Zero state is + * - All Readable, None Writeable, None Virtualized + */ + perm->virt = vzalloc(tmp); + perm->write = vzalloc(tmp); + perm->ent0eo = vzalloc(tmp); + perm->entnro = vzalloc(tmp); + perm->exist = vzalloc(tmp); + if (!perm->virt || !perm->write || !perm->ent0eo || + !perm->entnro || !perm->exist) { + free_perm_bits(perm); + return -ENOMEM; + } + + perm->readfn = vfio_ub_default_config_read; + perm->writefn = vfio_ub_default_config_write; + + return 0; +} + +static void init_ub_cfg_chunk_perms(struct perm_bits *perm, u32 pos, + u32 size, u32 virt, u32 write) +{ + for (u32 idx = 0; idx < size; idx++) + p_setd(perm, pos + idx * DWORD_SIZE, virt, write); +} + +static void init_ub_cfg_chunk_attrs(struct perm_bits *perm, u32 pos, u32 size, + u32 ent0eo, u32 entnro, u32 exist) +{ + for (u32 idx = 0; idx < size; idx++) + p_attr_setd(perm, pos + idx * DWORD_SIZE, ent0eo, entnro, exist); +} + +static int vfio_ub_cfg0_basic_read(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 *val) +{ + count = vfio_ub_default_config_read(vdev, pos, count, val); + + return count; +} + +static int vfio_ub_cfg0_basic_write(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 val) +{ + count = vfio_ub_default_config_write(vdev, pos, count, val); + + return count; +} + +static void init_ub_cfg0_basic_operation_perm(struct perm_bits *perm) +{ + p_setd(perm, UB_CFG0_BASIC_SLICE, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_TOTAL_NUMBER_PORT, ALL_VIRT, NO_WRITE); + + init_ub_cfg_chunk_perms(perm, UB_CFG0_CAP_BITMAP, + UB_CFG_BITMAP_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_perms(perm, UB_FEATURE_SUPPORT_0, + UB_CFG0_SUPPORT_FEATURE_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_perms(perm, UB_GUID, UB_GUID_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_perms(perm, UB_EID_0, UB_EID_SIZE / DWORD_SIZE, + ALL_VIRT, ALL_WRITE); + init_ub_cfg_chunk_perms(perm, UB_FM_EID_0, UB_FM_EID_SIZE / DWORD_SIZE, + ALL_VIRT, ALL_WRITE); + + p_setd(perm, UB_PRIMARY_CNA, UB_PRIMARY_CNA_MASK, UB_PRIMARY_CNA_MASK); + p_setd(perm, UB_UEID_0, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_UEID_1, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_UEID_2, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_UEID_3, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_UCNA, UB_UCNA_MASK, UB_UCNA_MASK); + + p_setw(perm, UB_UPI, (u16)ALL_VIRT, (u16)ALL_WRITE); + p_setd(perm, UB_MODULE, ALL_VIRT, NO_WRITE); + p_setb(perm, UB_ENTITY_RST, (u8)ALL_VIRT, UB_ENTITY_RST_BIT); + p_setb(perm, UB_MTU_CFG, (u8)ALL_VIRT, UB_MTU_CFG_BITS); + p_setb(perm, UB_CC_EN, (u8)ALL_VIRT, UB_CC_EN_BIT); + p_setb(perm, UB_TH_EN, (u8)ALL_VIRT, UB_TH_EN_BIT); + p_setd(perm, UB_FM_CNA, UB_FM_CNA_MASK, UB_FM_CNA_MASK); +} + +static void init_ub_cfg0_basic_attr_perm(struct perm_bits *perm) +{ + p_attr_setw(perm, UB_TOTAL_NUMBER_ENTITIES, (u16)ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + init_ub_cfg_chunk_attrs(perm, UB_FM_EID_0, UB_FM_EID_SIZE / DWORD_SIZE, + ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + + p_attr_setd(perm, UB_PRIMARY_CNA, NO_ENTITY0_EO, ENTITYN_RO, FUNC_EXIST); + p_attr_setd(perm, UB_PRIMARY_CNA + 0x4, NO_ENTITY0_EO, ENTITYN_RO, FUNC_NO_EXIST); + p_attr_setd(perm, UB_PRIMARY_CNA + 0x8, NO_ENTITY0_EO, ENTITYN_RO, FUNC_NO_EXIST); + p_attr_setd(perm, UB_PRIMARY_CNA + 0xc, NO_ENTITY0_EO, ENTITYN_RO, FUNC_NO_EXIST); + p_attr_setd(perm, UB_PRIMARY_CNA + 0x10, NO_ENTITY0_EO, ENTITYN_RO, FUNC_NO_EXIST); + + p_attr_setd(perm, UB_ENTITY_RST, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + p_attr_setb(perm, UB_MTU_CFG, (u8)NO_ENTITY0_EO, (u8)ENTITYN_RO, (u8)FUNC_EXIST); + p_attr_setb(perm, UB_CC_EN, (u8)NO_ENTITY0_EO, (u8)ENTITYN_RO, (u8)FUNC_EXIST); + p_attr_setd(perm, UB_TH_EN, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + p_attr_setd(perm, UB_FM_CNA, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); +} + +static int init_ub_cfg0_basic_perm(struct perm_bits *perm) +{ + if (alloc_perm_bits(perm, UB_SLICE_SZ)) + return -ENOMEM; + + perm->readfn = vfio_ub_cfg0_basic_read; + perm->writefn = vfio_ub_cfg0_basic_write; + + init_ub_cfg0_basic_operation_perm(perm); + init_ub_cfg0_basic_attr_perm(perm); + return 0; +} + +static int init_ub_cfg0_cap_perm(struct perm_bits *perm) +{ + return 0; +} + +static int vfio_ub_cfg1_basic_read(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 *val) +{ + count = vfio_ub_default_config_read(vdev, pos, count, val); + + return count; +} + +static int vfio_ub_cfg1_basic_write(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 val) +{ + u8 *elr; + u8 *buf; + + count = vfio_ub_default_config_write(vdev, pos, count, val); + if (count < 0) + return count; + + if (pos == UB_ENTITY_RS_ACCESS_EN) + ub_entity_enable(vdev->uent, val & 0x1); + + buf = vfio_ub_find_cfg_buf(vdev, UB_CFG1_BASIC_CAP); + if (!buf) + return -EFAULT; + + elr = buf + UB_ELR - UB_CFG1_BASIC; + if (*elr & UB_ELR_BIT) { + *elr = *elr & (u8)~UB_ELR_BIT; + if (ub_reset_entity(vdev->uent)) + ub_warn(vdev->uent, "do elr reset failed\n"); + } + + return count; +} + +static void init_ub_cfg1_basic_operation_perm(struct perm_bits *perm) +{ + p_setd(perm, UB_CFG1_BASIC_SLICE - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_perms(perm, UB_CFG1_CAP_BITMAP - UB_CFG1_BASIC, + UB_CFG_BITMAP_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_perms(perm, UB_CFG1_SUPPORT_FEATURE_L - UB_CFG1_BASIC, + UB_CFG1_SUPPORT_FEATURE_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + + p_setd(perm, UB_ERS0_SS - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS1_SS - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS2_SS - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS0_SA_L - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS0_SA_H - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS1_SA_L - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS1_SA_H - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS2_SA_L - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS2_SA_H - UB_CFG1_BASIC, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_ERS0_UBBA_L - UB_CFG1_BASIC, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_ERS0_UBBA_H - UB_CFG1_BASIC, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_ERS1_UBBA_L - UB_CFG1_BASIC, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_ERS1_UBBA_H - UB_CFG1_BASIC, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_ERS2_UBBA_L - UB_CFG1_BASIC, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_ERS2_UBBA_H - UB_CFG1_BASIC, ALL_VIRT, ALL_WRITE); + + p_setd(perm, UB_ELR - UB_CFG1_BASIC, ALL_VIRT, UB_ELR_BIT); + p_setd(perm, UB_ELR_DONE - UB_CFG1_BASIC, ~UB_ELR_DONE_BIT, NO_WRITE); + p_setd(perm, UB_SYS_PGS - UB_CFG1_BASIC, ALL_VIRT, UB_SYS_PGS_SIZE); + + p_setd(perm, UB_ENTITY_TOKEN_ID - UB_CFG1_BASIC, NO_VIRT, UB_TOKEN_ID_MASK); + p_setd(perm, UB_BUS_ACCESS_EN - UB_CFG1_BASIC, NO_VIRT, + UB_BUS_ACCESS_EN_BIT); + p_setd(perm, UB_ENTITY_RS_ACCESS_EN - UB_CFG1_BASIC, NO_VIRT, + UB_BUS_ACCESS_EN_BIT); +} + +static void init_ub_cfg1_basic_attr_perm(struct perm_bits *perm) +{ + p_attr_setd(perm, UB_ERS0_SA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + IDEV_NO_EXIST); + p_attr_setd(perm, UB_ERS0_SA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + IDEV_NO_EXIST); + + p_attr_setd(perm, UB_ERS1_SA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + IDEV_NO_EXIST); + p_attr_setd(perm, UB_ERS1_SA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + IDEV_NO_EXIST); + + p_attr_setd(perm, UB_ERS2_SA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + IDEV_NO_EXIST); + p_attr_setd(perm, UB_ERS2_SA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + IDEV_NO_EXIST); + + p_attr_setd(perm, UB_ERS0_UBBA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, ENTITYN_RO, + UDEV_NO_EXIST); + p_attr_setd(perm, UB_ERS0_UBBA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, ENTITYN_RO, + UDEV_NO_EXIST); + + p_attr_setd(perm, UB_ERS1_UBBA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, ENTITYN_RO, + UDEV_NO_EXIST); + p_attr_setd(perm, UB_ERS1_UBBA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, ENTITYN_RO, + UDEV_NO_EXIST); + + p_attr_setd(perm, UB_ERS2_UBBA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, ENTITYN_RO, + UDEV_NO_EXIST); + p_attr_setd(perm, UB_ERS2_UBBA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, ENTITYN_RO, + UDEV_NO_EXIST); + + p_attr_setd(perm, UB_SYS_PGS - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + FUNC_NO_EXIST); + p_attr_setd(perm, UB_EU_TBA_L - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + FUNC_NO_EXIST); + p_attr_setd(perm, UB_EU_TBA_H - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + FUNC_NO_EXIST); + p_attr_setd(perm, UB_EU_TEN - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + FUNC_NO_EXIST); + p_attr_setd(perm, UB_CLASS_CODE - UB_CFG1_BASIC, NO_ENTITY0_EO, NO_ENTITYN_RO, + FUNC_EXIST); +} + +static int init_ub_cfg1_basic_perm(struct perm_bits *perm) +{ + if (alloc_perm_bits(perm, UB_SLICE_SZ)) + return -ENOMEM; + + perm->readfn = vfio_ub_cfg1_basic_read; + perm->writefn = vfio_ub_cfg1_basic_write; + + init_ub_cfg1_basic_operation_perm(perm); + init_ub_cfg1_basic_attr_perm(perm); + + return 0; +} + +static int vfio_ub_int_type2_cap_read(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 *val) +{ + count = vfio_ub_default_config_read(vdev, pos, count, val); + + return count; +} + +static int vfio_ub_int_type2_cap_write(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 val) +{ + count = vfio_ub_default_config_write(vdev, pos, count, val); + + return count; +} + +static int init_ub_cfg1_int_cap_perm(struct perm_bits *perm) +{ + if (alloc_perm_bits(perm, UB_SLICE_SZ)) + return -ENOMEM; + + perm->readfn = vfio_ub_int_type2_cap_read; + perm->writefn = vfio_ub_int_type2_cap_write; + + p_setd(perm, UB_INT_CAP_SLICE - UB_CFG1_CAP_INT_CAP, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_INFO - UB_CFG1_CAP_INT_CAP, ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_VECTOR_TBL_SA_L - UB_CFG1_CAP_INT_CAP, + ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_VECTOR_TBL_SA_H - UB_CFG1_CAP_INT_CAP, + ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_ADDR_TBL_SA_L - UB_CFG1_CAP_INT_CAP, + ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_ADDR_TBL_SA_H - UB_CFG1_CAP_INT_CAP, + ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_PENDING_TBL_SA_L - UB_CFG1_CAP_INT_CAP, + ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_PENDING_TBL_SA_H - UB_CFG1_CAP_INT_CAP, + ALL_VIRT, NO_WRITE); + p_setd(perm, UB_INT_ID - UB_CFG1_CAP_INT_CAP, ALL_VIRT, ALL_WRITE); + p_setd(perm, UB_INT_MASK - UB_CFG1_CAP_INT_CAP, UB_INT_MASK_BIT, + UB_INT_MASK_BIT); + + p_setd(perm, UB_INT_EN - UB_CFG1_CAP_INT_CAP, UB_INT_EN_BIT, + UB_INT_EN_BIT); + + return 0; +} + +static int init_ub_cfg1_cap_perm(struct perm_bits *perm) +{ + return init_ub_cfg1_int_cap_perm(&perm[UB_INT_TYPE2_CAP]); +} + +static int vfio_ub_port_basic_read(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 *val) +{ + count = vfio_ub_default_config_read(vdev, pos, count, val); + + return count; +} + +static int vfio_ub_port_basic_write(struct vfio_ub_core_device *vdev, u64 pos, + int count, __le32 val) +{ + count = vfio_ub_default_config_write(vdev, pos, count, val); + + return count; +} + +static int init_ub_port_basic_perm(struct perm_bits *perm) +{ + if (alloc_perm_bits(perm, UB_SLICE_SZ)) + return -ENOMEM; + + perm->readfn = vfio_ub_port_basic_read; + perm->writefn = vfio_ub_port_basic_write; + + p_setd(perm, UB_CFG0_PORT_SLICE, ALL_VIRT, NO_WRITE); + p_attr_setd(perm, UB_CFG0_PORT_SLICE, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + + init_ub_cfg_chunk_perms(perm, UB_CFG0_PORT_BITMAP, + UB_CFG_BITMAP_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_attrs(perm, UB_CFG0_PORT_BITMAP, + UB_CFG_BITMAP_SIZE / DWORD_SIZE, + ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + + p_setd(perm, UB_PORT_INFO, ALL_VIRT, NO_WRITE); + p_attr_setd(perm, UB_PORT_INFO, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + + init_ub_cfg_chunk_perms(perm, UB_NEIGHBOR_PORT_INFO, + UB_UB_NEIGHBOR_PORT_INFO_SIZE / DWORD_SIZE, + ALL_VIRT, NO_WRITE); + init_ub_cfg_chunk_attrs(perm, UB_NEIGHBOR_PORT_INFO, + UB_UB_NEIGHBOR_PORT_INFO_SIZE / DWORD_SIZE, + ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + + p_setd(perm, UB_PORT_NET_ADDR, UB_PORT_NET_ADDR_MASK, UB_PORT_NET_ADDR_MASK); + p_attr_setd(perm, UB_PORT_NET_ADDR, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + p_setd(perm, UB_PORT_RST, ALL_VIRT, UB_PORT_RST_BIT); + p_attr_setd(perm, UB_PORT_RST, ENTITY0_EO, NO_ENTITYN_RO, FUNC_EXIST); + + return 0; +} + +static u8 vfio_slice_supported[UB_CFG1_MAX_CAP + 1]; +static void vfio_ub_init_slice_supported(void) +{ + vfio_slice_supported[UB_CFG0_BASIC_CAP] = 1; + vfio_slice_supported[UB_CFG1_BASIC_CAP] = 1; + vfio_slice_supported[UB_INT_TYPE2_CAP] = 1; +} + +int __init vfio_ub_init_perm_bits(void) +{ + int ret; + + vfio_ub_init_slice_supported(); + + ret = init_ub_cfg0_basic_perm(&cap_perms[UB_CFG0_BASIC_CAP]); + ret |= init_ub_cfg0_cap_perm(cap_perms); + + ret |= init_ub_cfg1_basic_perm(&cap_perms[UB_CFG1_BASIC_CAP]); + ret |= init_ub_cfg1_cap_perm(cap_perms); + + ret |= init_ub_port_basic_perm(&port_basic_perms); + + if (ret) + vfio_ub_uninit_perm_bits(); + + return ret; +} + +void vfio_ub_uninit_perm_bits(void) +{ + free_perm_bits(&port_basic_perms); + free_perm_bits(&cap_perms[UB_INT_TYPE2_CAP]); + free_perm_bits(&cap_perms[UB_CFG1_BASIC_CAP]); + free_perm_bits(&cap_perms[UB_CFG0_BASIC_CAP]); +} + +static int vfio_ub_fill_slice_vconfig(struct vfio_ub_core_device *vdev, + int num, u32 pos, u32 size) +{ + __le32 *dwordp; + u32 val = 0; + int ret; + u32 cap_id; + u32 start = 0; + + cap_id = UB_CFG_POS_TO_CAP(pos); + + for (u32 i = 0; i < size; i += DWORD_SIZE) { + ret = vfio_ub_user_config_read(vdev->uent, pos + i, &val, DWORD_SIZE); + if (ret) { + ub_info(vdev->uent, "read cfgspace pos[%#x] failed, used default val 0\n", + pos + i); + val = 0; + } + dwordp = (__le32 *)(&vdev->vconfig.slice[num].cfg[i + start]); + *dwordp = cpu_to_le32(val); + } + + /* vfio-ub support no port caps for userspace */ + if (cap_id >= UB_PORT0_BASIC_CAP) + memset(&vdev->vconfig.slice[num].cfg[UB_CFG0_PORT_BITMAP], 0, UB_CFG_BITMAP_SIZE); + + return 0; +} + +static struct ub_entity *ub_get_primary_ent(struct ub_entity *uent) +{ + if (uent->entity_idx == 0) + return uent; + + return uent->is_mue ? uent->pue : uent->pue->pue; +} + +static int vfio_ub_init_slice_config_info(struct vfio_ub_core_device *vdev, + u32 cap_id) +{ + struct vfio_ub_slice *pslice, *realloc_slice, *cur_slice; + struct ub_entity *primary_dev; + int used_size; + u32 pos; + int ret; + u32 val; + + pos = UB_CFG_CAP_TO_POS(cap_id); + if (cap_id != UB_PORT0_BASIC_CAP) { + ret = ub_cfg_read_dword(vdev->uent, pos, &val); + } else { + primary_dev = ub_get_primary_ent(vdev->uent); + ret = ub_cfg_read_dword(primary_dev, pos, &val); + } + + if (ret) { + ub_err(vdev->uent, "get cap[%#x] slice header failed\n", cap_id); + return ret; + } + used_size = (val >> SZ_4) << SZ_2; + + if (used_size == 0 || used_size > UB_SLICE_SZ) { + ub_err(vdev->uent, "invalid cap_id[%#x] used_size[%d]\n", cap_id, used_size); + return -EINVAL; + } + + pslice = vdev->vconfig.slice; + realloc_slice = (struct vfio_ub_slice *)krealloc(vdev->vconfig.slice, + (vdev->vconfig.nums + 1) * + sizeof(struct vfio_ub_slice), GFP_KERNEL); + if (realloc_slice == NULL) + return -ENOMEM; + + vdev->vconfig.slice = realloc_slice; + + cur_slice = vdev->vconfig.slice + vdev->vconfig.nums; + cur_slice->cfg = kzalloc(used_size, GFP_KERNEL); + if (cur_slice->cfg == NULL) { + if (pslice == NULL) { + kfree(vdev->vconfig.slice); + vdev->vconfig.slice = NULL; + } + return -ENOMEM; + } + + cur_slice->used_size = used_size; + cur_slice->cap_id = cap_id; + ret = vfio_ub_fill_slice_vconfig(vdev, vdev->vconfig.nums, pos, used_size); + if (ret) { + kfree(cur_slice->cfg); + if (pslice == NULL) { + kfree(vdev->vconfig.slice); + vdev->vconfig.slice = NULL; + } + return ret; + } + + if (cap_id <= UB_CFG1_MAX_CAP) + vdev->vconfig.map[cap_id] = vdev->vconfig.nums; + vdev->vconfig.nums++; + + return 0; +} + +static int vfio_ub_find_cfg_cap(struct vfio_ub_core_device *vdev, u32 cap_id) +{ + int idx; + + for (idx = 0; idx < vdev->vconfig.nums; idx++) { + if (vdev->vconfig.slice[idx].cap_id == cap_id) + return idx; + } + + return CFG_MAP_INVALID; +} + +static void vfio_ub_uninit_slice_config_info(struct vfio_ub_core_device *vdev, + u32 cap_id) +{ + u32 tmp_cap; + int num; + + if (cap_id <= UB_CFG1_MAX_CAP) { + num = vdev->vconfig.map[cap_id]; + vdev->vconfig.map[cap_id] = CFG_MAP_INVALID; + } else { + num = vfio_ub_find_cfg_cap(vdev, cap_id); + } + + if (num == CFG_MAP_INVALID) + return; + + kfree(vdev->vconfig.slice[num].cfg); + for (int i = num; i < vdev->vconfig.nums - 1; i++) { + memcpy(vdev->vconfig.slice + i, vdev->vconfig.slice + i + 1, + sizeof(struct vfio_ub_slice)); + tmp_cap = vdev->vconfig.slice[i].cap_id; + if (tmp_cap <= UB_CFG1_MAX_CAP) + vdev->vconfig.map[tmp_cap] = i; + } + + vdev->vconfig.nums--; +} + +static int vfio_ub_init_cfg0_basic_info(struct vfio_ub_core_device *vdev) +{ + u8 *cfg0_bitmap_buf; + int cfg0_basic_idx; + int i, j, cap_idx = 0; + int ret; + + ret = vfio_ub_init_slice_config_info(vdev, UB_CFG0_BASIC_CAP); + if (ret) + return ret; + + cfg0_basic_idx = vdev->vconfig.map[UB_CFG0_BASIC_CAP]; + cfg0_bitmap_buf = vdev->vconfig.slice[cfg0_basic_idx].cfg + + UB_CFG0_CAP_BITMAP; + + for (i = 0; i < UB_CFG_BITMAP_SIZE; i++) { + for (j = 0; j < BITS_PER_BYTE; j++) { + if (((cfg0_bitmap_buf[i] >> j) & 1) && + vfio_slice_supported[cap_idx]) { + vdev->vconfig.final_slice_supported[cap_idx] = 1; + } else if (((cfg0_bitmap_buf[i] >> j) & 1) && + vfio_slice_supported[cap_idx] == 0) { + cfg0_bitmap_buf[i] = cfg0_bitmap_buf[i] & + ~(1 << j); + } + cap_idx++; + } + } + + return 0; +} + +static void vfio_ub_uninit_cfg0_basic_info(struct vfio_ub_core_device *vdev) +{ + u32 cap_id; + + for (cap_id = 0; cap_id <= UB_CFG0_MAX_CAP; cap_id++) { + if (vdev->vconfig.final_slice_supported[cap_id] == 1) + vdev->vconfig.final_slice_supported[cap_id] = 0; + } + + vfio_ub_uninit_slice_config_info(vdev, UB_CFG0_BASIC_CAP); +} + +static int vfio_ub_init_cfg0_cap_info(struct vfio_ub_core_device *vdev) +{ + int cap_id; + int ret; + + for (cap_id = 1; cap_id <= UB_CFG0_MAX_CAP; cap_id++) { + if (vdev->vconfig.final_slice_supported[cap_id]) { + ret = vfio_ub_init_slice_config_info(vdev, (u32)cap_id); + if (ret) + goto cfg0_cap_init_err; + } + } + + return 0; + +cfg0_cap_init_err: + for (cap_id = cap_id - 1; cap_id >= 1; cap_id--) + vfio_ub_uninit_slice_config_info(vdev, cap_id); + return ret; +} + +static void vfio_ub_uninit_cfg0_cap_info(struct vfio_ub_core_device *vdev) +{ + u32 cap_id; + + for (cap_id = 1; cap_id <= UB_CFG0_MAX_CAP; cap_id++) { + if (vdev->vconfig.final_slice_supported[cap_id]) + vfio_ub_uninit_slice_config_info(vdev, cap_id); + } +} + +static int vfio_ub_init_cfg1_basic_info(struct vfio_ub_core_device *vdev) +{ + u32 cap_idx = UB_CFG1_BASIC_CAP; + u8 *cfg1_bitmap_buf; + int cfg1_basic_idx; + int i, j; + int ret; + + ret = vfio_ub_init_slice_config_info(vdev, UB_CFG1_BASIC_CAP); + if (ret) + return ret; + + cfg1_basic_idx = vdev->vconfig.map[UB_CFG1_BASIC_CAP]; + cfg1_bitmap_buf = vdev->vconfig.slice[cfg1_basic_idx].cfg + + UB_CFG1_CAP_BITMAP - UB_CFG1_BASIC; + + for (i = 0; i < UB_CFG_BITMAP_SIZE; i++) { + for (j = 0; j < BITS_PER_BYTE; j++) { + if (((cfg1_bitmap_buf[i] >> j) & 1) && + vfio_slice_supported[cap_idx]) { + vdev->vconfig.final_slice_supported[cap_idx] = 1; + } else if (((cfg1_bitmap_buf[i] >> j) & 1) && + vfio_slice_supported[cap_idx] == 0) { + cfg1_bitmap_buf[i] = cfg1_bitmap_buf[i] & + ~(1 << j); + } + cap_idx++; + } + } + + return 0; +} + +static void vfio_ub_uninit_cfg1_basic_info(struct vfio_ub_core_device *vdev) +{ + u32 cap_id; + + for (cap_id = UB_DECODER_CAP; cap_id <= UB_CFG1_MAX_CAP; cap_id++) { + if (vdev->vconfig.final_slice_supported[cap_id] == 1) + vdev->vconfig.final_slice_supported[cap_id] = 0; + } + + vfio_ub_uninit_slice_config_info(vdev, UB_CFG1_BASIC_CAP); +} + +static int vfio_ub_init_cfg1_cap_info(struct vfio_ub_core_device *vdev) +{ + u32 cap_id; + int ret; + + for (cap_id = UB_DECODER_CAP; cap_id <= UB_CFG1_MAX_CAP; cap_id++) { + if (vdev->vconfig.final_slice_supported[cap_id]) { + ret = vfio_ub_init_slice_config_info(vdev, cap_id); + if (ret) + goto cfg1_cap_init_err; + } + } + + return 0; + +cfg1_cap_init_err: + for (cap_id = cap_id - 1; cap_id >= UB_DECODER_CAP; cap_id--) + vfio_ub_uninit_slice_config_info(vdev, cap_id); + return ret; +} + +static void vfio_ub_uninit_cfg1_cap_info(struct vfio_ub_core_device *vdev) +{ + u32 cap_id; + + for (cap_id = UB_DECODER_CAP; cap_id <= UB_CFG1_MAX_CAP; cap_id++) { + if (vdev->vconfig.final_slice_supported[cap_id]) + vfio_ub_uninit_slice_config_info(vdev, cap_id); + } +} + +static void vfio_ub_init_port_cap_bitmap(struct vfio_ub_core_device *vdev, int idx) +{ + /* now we do not support any port cap */ + memset(vdev->vconfig.slice[idx].cfg + UB_CFG0_PORT_BITMAP, + 0, UB_CFG_BITMAP_SIZE); +} + +static int vfio_ub_init_port_basic_info(struct vfio_ub_core_device *vdev) +{ + int cfg0_basic_idx = vdev->vconfig.map[UB_CFG0_BASIC_CAP]; + u16 total_port_nums; + __le16 *wordp; + int ret; + int idx; + + wordp = (__le16 *)(vdev->vconfig.slice[cfg0_basic_idx].cfg + + UB_TOTAL_NUMBER_PORT); + total_port_nums = le16_to_cpu(*wordp); + + for (idx = 0; idx < total_port_nums; idx++) { + ret = vfio_ub_init_slice_config_info(vdev, UB_PORT0_BASIC_CAP + + idx * UB_PORT_CAP_GAP); + if (ret) + goto port_basic_init_err; + vfio_ub_init_port_cap_bitmap(vdev, vdev->vconfig.nums - 1); + } + + return 0; + +port_basic_init_err: + for (idx = idx - 1; idx >= 0; idx--) + vfio_ub_uninit_slice_config_info(vdev, UB_PORT0_BASIC_CAP + + idx * UB_PORT_CAP_GAP); + return ret; +} + +static void vfio_ub_uninit_port_basic_info(struct vfio_ub_core_device *vdev) +{ + int cfg0_basic_idx = vdev->vconfig.map[UB_CFG0_BASIC_CAP]; + u16 total_port_nums; + u16 idx; + __le16 *wordp; + + wordp = (__le16 *)(vdev->vconfig.slice[cfg0_basic_idx].cfg + + UB_TOTAL_NUMBER_PORT); + total_port_nums = le16_to_cpu(*wordp); + for (idx = 0; idx < total_port_nums; idx++) + vfio_ub_uninit_slice_config_info(vdev, UB_PORT0_BASIC_CAP + + idx * UB_PORT_CAP_GAP); +} + +int vfio_ub_config_init(struct vfio_ub_core_device *vdev) +{ + int ret; + + memset(vdev->vconfig.map, CFG_MAP_INVALID, + (UB_CFG1_MAX_CAP + 1) * sizeof(int)); + + ret = vfio_ub_init_cfg0_basic_info(vdev); + if (ret) + return ret; + + ret = vfio_ub_init_cfg0_cap_info(vdev); + if (ret) + goto out_uninit_cfg0_basic; + + ret = vfio_ub_init_cfg1_basic_info(vdev); + if (ret) + goto out_uninit_cfg0_cap; + + ret = vfio_ub_init_cfg1_cap_info(vdev); + if (ret) + goto out_uninit_cfg1_basic; + + ret = vfio_ub_init_port_basic_info(vdev); + if (ret) + goto out_uninit_cfg1_cap; + + return 0; + +out_uninit_cfg1_cap: + vfio_ub_uninit_cfg1_cap_info(vdev); +out_uninit_cfg1_basic: + vfio_ub_uninit_cfg1_basic_info(vdev); +out_uninit_cfg0_cap: + vfio_ub_uninit_cfg0_cap_info(vdev); +out_uninit_cfg0_basic: + vfio_ub_uninit_cfg0_basic_info(vdev); + + memset(vdev->vconfig.map, CFG_MAP_INVALID, (UB_CFG1_MAX_CAP + 1) * sizeof(int)); + kfree(vdev->vconfig.slice); + vdev->vconfig.slice = NULL; + vdev->vconfig.nums = 0; + return ret; +} + +void vfio_ub_config_uninit(struct vfio_ub_core_device *vdev) +{ + if (vdev->vconfig.slice == NULL) + return; + + vfio_ub_uninit_port_basic_info(vdev); + vfio_ub_uninit_cfg1_cap_info(vdev); + vfio_ub_uninit_cfg1_basic_info(vdev); + vfio_ub_uninit_cfg0_cap_info(vdev); + vfio_ub_uninit_cfg0_basic_info(vdev); + + memset(vdev->vconfig.map, CFG_MAP_INVALID, (UB_CFG1_MAX_CAP + 1) * sizeof(int)); + kfree(vdev->vconfig.slice); + vdev->vconfig.slice = NULL; + vdev->vconfig.nums = 0; +} + +static int vfio_ub_find_valid_cap(struct vfio_ub_core_device *vdev, u32 cap_id) +{ + int i; + + if (cap_id <= UB_CFG1_MAX_CAP) { + if (vdev->vconfig.map[cap_id] != CFG_MAP_INVALID) + return 0; + } else { + for (i = 0; i < vdev->vconfig.nums; i++) { + if (vdev->vconfig.slice[i].cap_id == cap_id) + return 0; + } + } + + return -ENOENT; +} + +static int vfio_ub_find_cap_used_size(struct vfio_ub_core_device *vdev, u32 cap_id) +{ + int i; + int num; + + if (cap_id <= UB_CFG1_MAX_CAP) { + num = vdev->vconfig.map[cap_id]; + return vdev->vconfig.slice[num].used_size; + } + + for (i = 0; i < vdev->vconfig.nums; i++) { + if (vdev->vconfig.slice[i].cap_id == cap_id) + return vdev->vconfig.slice[i].used_size; + } + + return -ENOENT; +} + +static ssize_t vfio_ub_config_rw_used_zone(struct vfio_ub_core_device *vdev, + char __user *buf, size_t count, + loff_t *ppos, bool iswrite) +{ + size_t cap_start_pos; + struct perm_bits *perm; + size_t left; + u32 cap_id; + __le32 val = 0; + int used_size; + int ret; + + cap_id = UB_CFG_POS_TO_CAP(*ppos); + cap_start_pos = UB_CFG_CAP_TO_POS(cap_id); + used_size = vfio_ub_find_cap_used_size(vdev, cap_id); + + left = cap_start_pos + used_size - *ppos; + + perm = vfio_ub_find_cap_perm(cap_id); + + count = min(count, left); + if (count >= DWORD_SIZE && !(*ppos % DWORD_SIZE)) + count = DWORD_SIZE; + else if (count >= WORD_SIZE && !(*ppos % WORD_SIZE)) + count = WORD_SIZE; + else + count = BYTE_SIZE; + + ret = count; + + if (iswrite) { + if (!perm->writefn) + return ret; + + if (copy_from_user(&val, buf, count)) + return -EFAULT; + + ret = perm->writefn(vdev, *ppos, count, val); + } else { + if (perm->readfn) { + ret = perm->readfn(vdev, *ppos, count, &val); + if (ret < 0) + return ret; + } + + if (copy_to_user(buf, &val, count)) + return -EFAULT; + } + + return ret; +} + +static ssize_t vfio_ub_config_rw_unused_zone(struct vfio_ub_core_device *vdev, + char __user *buf, size_t count, + loff_t *ppos, bool iswrite) +{ + size_t cap_start_pos; + size_t left; + u32 cap_id; + + cap_id = UB_CFG_POS_TO_CAP(*ppos); + cap_start_pos = UB_CFG_CAP_TO_POS(cap_id); + left = cap_start_pos + UB_SLICE_SZ - *ppos; + + count = min(count, left); + if (iswrite) + return count; + + /* read unused zone just return zero */ + if (copy_to_user(buf, zero_array, count)) + return -EFAULT; + + return count; +} + +static ssize_t vfio_ub_config_do_rw(struct vfio_ub_core_device *vdev, + char __user *buf, size_t count, + loff_t *ppos, bool iswrite) +{ + u32 cap_id; + ssize_t ret; + size_t cap_start_pos; + int used_size; + + if (*ppos < 0 || *ppos >= UB_ROUTE_TABLE_SLICE_START || + *ppos + count > UB_ROUTE_TABLE_SLICE_START || + count > UB_SLICE_SZ) + return -EFAULT; + + cap_id = UB_CFG_POS_TO_CAP(*ppos); + + ret = vfio_ub_find_valid_cap(vdev, cap_id); + if (ret < 0) { + ret = vfio_ub_config_rw_unused_zone(vdev, buf, + count, ppos, iswrite); + } else { + cap_start_pos = UB_CFG_CAP_TO_POS(cap_id); + used_size = vfio_ub_find_cap_used_size(vdev, cap_id); + if (used_size < 0) + return -EINVAL; + + if (*ppos < cap_start_pos + used_size) { + ret = vfio_ub_config_rw_used_zone(vdev, buf, + count, ppos, iswrite); + } else { + ret = vfio_ub_config_rw_unused_zone(vdev, buf, + count, ppos, iswrite); + } + } + + return ret; +} + +ssize_t vfio_ub_config_rw(struct vfio_ub_core_device *vdev, char __user *buf, + size_t count, loff_t *ppos, bool iswrite) +{ + size_t done = 0; + int ret = 0; + loff_t pos = *ppos; + + pos &= VFIO_UB_OFFSET_MASK; + + while (count) { + ret = vfio_ub_config_do_rw(vdev, buf, count, &pos, iswrite); + if (ret < 0) + return ret; + + buf += ret; + pos += ret; + count -= ret; + done += ret; + } + + *ppos += done; + + return done; +} diff --git a/drivers/vfio/ubus/vfio_ub_core.c b/drivers/vfio/ubus/vfio_ub_core.c new file mode 100644 index 0000000000000000000000000000000000000000..7c42ff90ed168e2d73f748eb0b0549222cbb0f51 --- /dev/null +++ b/drivers/vfio/ubus/vfio_ub_core.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + * + * Thanks to Alex Williamson and Tom Lyon for their original + * vfio implementation. + * + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "vfio ub: " fmt + +#include +#include +#include +#include + +#include "vfio_ub_private.h" + +static bool is_ub_entity_support_used(struct ub_entity *uent) +{ + if (!((uent_type(uent) == UB_TYPE_CONTROLLER || + uent_type(uent) == UB_TYPE_ICONTROLLER) && + uent_base_code(uent) != UB_BASE_CODE_BUS_CONTROLLER)) + return false; + + if (!list_empty(&uent->ue_list)) + return false; + + return true; +} + +int vfio_ub_core_register_device(struct vfio_ub_core_device *vdev) +{ + struct ub_entity *uent = vdev->uent; + struct device *dev = &uent->dev; + + /* Drivers must set the vfio_ub_core_device to their drvdata */ + if (WARN_ON(vdev != dev_get_drvdata(dev))) + return -EINVAL; + + if (!is_ub_entity_support_used(uent)) { + ub_err(uent, "Cannot bind to vfio driver\n"); + return -EBUSY; + } + + return vfio_register_group_dev(&vdev->vdev); +} + +void vfio_ub_core_unregister_device(struct vfio_ub_core_device *vdev) +{ + vfio_unregister_group_dev(&vdev->vdev); +} + +static int vfio_usi_info_init(struct vfio_ub_core_device *vdev) +{ + struct ub_entity *uent = vdev->uent; + u32 low32; + u32 high32; + u16 num; + int ret; + + /* when device supports int type2, read usi info from cfgspace */ + if (uent->no_intr == 0 && uent->intr_type1 == 0) { + ret = ub_cfg_read_dword(vdev->uent, UB_INT_VECTOR_TBL_SA_L, &low32); + ret |= ub_cfg_read_dword(vdev->uent, UB_INT_VECTOR_TBL_SA_H, &high32); + ret |= ub_cfg_read_word(vdev->uent, UB_NUM_OF_INTR_VECTOR_TBL, &num); + if (ret) + return -EFAULT; + + vdev->usi_vector_offset = (u64)high32 << 32 | low32; + vdev->usi_vector_size = (num + 1) * UB_INTR_VECTOR_ENTRY_SIZE; + + ret = ub_cfg_read_dword(vdev->uent, UB_INT_ADDR_TBL_SA_L, &low32); + ret |= ub_cfg_read_dword(vdev->uent, UB_INT_ADDR_TBL_SA_H, &high32); + ret |= ub_cfg_read_word(vdev->uent, UB_NUM_OF_INTR_ADDR_TBL, &num); + if (ret) + return -EFAULT; + + vdev->usi_addr_offset = (u64)high32 << 32 | low32; + vdev->usi_addr_size = (num + 1) * UB_INTR_ADDR_ENTRY_SIZE; + } else { + vdev->usi_vector_offset = 0; + vdev->usi_vector_size = 0; + vdev->usi_addr_offset = 0; + vdev->usi_addr_size = 0; + } + + return 0; +} + +static int vfio_ub_enable(struct vfio_ub_core_device *vdev) +{ + int ret; + + vdev->num_ext_irqs = 0; + vdev->num_regions = 0; + vdev->num_vendor_irqs = 0; + vdev->num_vendor_regions = 0; + vdev->reset_works = 1; /* we assume ub support ELR for now. */ + + ret = vfio_usi_info_init(vdev); + if (ret) + return ret; + + return vfio_ub_config_init(vdev); +} + +static void vfio_ub_disable(struct vfio_ub_core_device *vdev) +{ + vfio_ub_config_uninit(vdev); + vfio_ub_set_irqs_ioctl(vdev, VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER, + vdev->irq_type, 0, 0, NULL); + + /* clear device caches when vm exit or crash */ + if (vdev->reset_works) + ub_reset_entity(vdev->uent); +} + +int vfio_ub_core_open_device(struct vfio_device *core_vdev) +{ + struct vfio_ub_core_device *vdev = container_of(core_vdev, + struct vfio_ub_core_device, vdev); + + return vfio_ub_enable(vdev); +} + +void vfio_ub_core_close_device(struct vfio_device *core_vdev) +{ + struct vfio_ub_core_device *vdev = container_of(core_vdev, + struct vfio_ub_core_device, vdev); + + vfio_ub_disable(vdev); + vfio_ub_unset_resmap(vdev); + + mutex_lock(&vdev->igate); + if (vdev->req_trigger) { + eventfd_ctx_put(vdev->req_trigger); + vdev->req_trigger = NULL; + } + mutex_unlock(&vdev->igate); +} + +static int vfio_ub_get_device_info(struct vfio_ub_core_device *vdev, unsigned long arg) +{ + unsigned long minsz = offsetofend(struct vfio_device_info, num_irqs); + struct vfio_device_info info; + + if (copy_from_user(&info, (void __user *)arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz) + return -EINVAL; + + info.flags = VFIO_DEVICE_FLAGS_UB; + + if (vdev->reset_works) + info.flags |= VFIO_DEVICE_FLAGS_RESET; + + info.num_regions = VFIO_UB_NUM_REGIONS + vdev->num_regions + + vdev->num_vendor_regions; + info.num_irqs = VFIO_UB_NUM_IRQS + vdev->num_ext_irqs + + vdev->num_vendor_irqs; + + return copy_to_user((void __user *)arg, &info, minsz) ? + -EFAULT : 0; +} + +static int vfio_ub_get_region_info(struct vfio_ub_core_device *vdev, unsigned long arg) +{ + unsigned long minsz = offsetofend(struct vfio_region_info, offset); + struct ub_entity *uent = vdev->uent; + struct vfio_region_info info; + + if (copy_from_user(&info, (void __user *)arg, minsz)) + return -EFAULT; + + if ((info.argsz < minsz) || + (info.index >= (VFIO_UB_NUM_REGIONS + vdev->num_regions))) + return -EINVAL; + + switch (info.index) { + case VFIO_UB_REGION0_INDEX: + case VFIO_UB_REGION1_INDEX: + case VFIO_UB_REGION2_INDEX: + info.offset = VFIO_UB_INDEX_TO_OFFSET(info.index); + info.size = ub_resource_len(uent, info.index); + if (!info.size) { + info.flags = 0; + break; + } + + info.flags = VFIO_REGION_INFO_FLAG_READ | + VFIO_REGION_INFO_FLAG_WRITE | + VFIO_REGION_INFO_FLAG_MMAP; + break; + case VFIO_UB_CONFIG_REGION_INDEX: + info.offset = VFIO_UB_INDEX_TO_OFFSET(info.index); + info.flags = VFIO_REGION_INFO_FLAG_READ | + VFIO_REGION_INFO_FLAG_WRITE; + info.size = 0; + break; + default: + return -EINVAL; + } + + return copy_to_user((void __user *)arg, &info, minsz) ? + -EFAULT : 0; +} + +static int vfio_ub_entity_reset(struct vfio_ub_core_device *vdev) +{ + if (!vdev->reset_works) + return -EINVAL; + + return ub_reset_entity(vdev->uent); +} + +static int vfio_ub_get_irq_count(struct vfio_ub_core_device *vdev, int irq_type) +{ + struct ub_entity *uent = vdev->uent; + + if (irq_type == VFIO_UB_INTR_IRQ_INDEX) { + if (!(uent->no_intr == 0 && uent->intr_type1 == 0)) + return 0; + return ub_intr_vec_count(vdev->uent); + } else if (irq_type == VFIO_UB_REQ_IRQ_INDEX) { + return 1; + } + + return 0; +} + +static int vfio_ub_get_irq_info(struct vfio_ub_core_device *vdev, unsigned long arg) +{ + unsigned long minsz = offsetofend(struct vfio_irq_info, count); + struct vfio_irq_info info; + int ret; + + if (copy_from_user(&info, (void __user *)arg, minsz)) + return -EFAULT; + + if ((info.argsz < minsz) || + (info.index >= (VFIO_UB_NUM_IRQS + vdev->num_ext_irqs))) + return -EINVAL; + + info.flags = VFIO_IRQ_INFO_EVENTFD | VFIO_IRQ_INFO_NORESIZE; + + switch (info.index) { + case VFIO_UB_INTR_IRQ_INDEX: + ret = vfio_ub_get_irq_count(vdev, info.index); + if (ret < 0) + return -EFAULT; + + info.count = (u32)ret; + break; + case VFIO_UB_REQ_IRQ_INDEX: + info.count = (u32)vfio_ub_get_irq_count(vdev, info.index); + break; + default: + return -EINVAL; + } + + return copy_to_user((void __user *)arg, &info, minsz) ? + -EFAULT : 0; +} + +static int vfio_ub_set_irqs(struct vfio_ub_core_device *vdev, unsigned long arg) +{ + struct vfio_irq_set hdr; + u32 sz = sizeof(hdr); + size_t data_size = 0; + u8 *data = NULL; + int max; + int ret; + + if (copy_from_user(&hdr, (void __user *)arg, sz)) + return -EFAULT; + + max = vfio_ub_get_irq_count(vdev, hdr.index); + if (max <= 0) + return -EINVAL; + + ret = vfio_set_irqs_validate_and_prepare(&hdr, max, + VFIO_UB_NUM_IRQS + vdev->num_ext_irqs, &data_size); + if (ret) + return ret; + + if (data_size) { + data = (u8 *)memdup_user((void __user *)(arg + sz), data_size); + if (IS_ERR(data)) + return PTR_ERR(data); + } + + mutex_lock(&vdev->igate); + ret = vfio_ub_set_irqs_ioctl(vdev, hdr.flags, hdr.index, hdr.start, + hdr.count, data); + mutex_unlock(&vdev->igate); + kfree(data); + + return ret; +} + +long vfio_ub_core_ioctl(struct vfio_device *core_vdev, unsigned int cmd, unsigned long arg) +{ + struct vfio_ub_core_device *vdev = container_of(core_vdev, + struct vfio_ub_core_device, vdev); + + switch (cmd) { + case VFIO_DEVICE_GET_INFO: + return vfio_ub_get_device_info(vdev, arg); + case VFIO_DEVICE_GET_REGION_INFO: + return vfio_ub_get_region_info(vdev, arg); + case VFIO_DEVICE_GET_IRQ_INFO: + return vfio_ub_get_irq_info(vdev, arg); + case VFIO_DEVICE_SET_IRQS: + return vfio_ub_set_irqs(vdev, arg); + case VFIO_DEVICE_RESET: + return vfio_ub_entity_reset(vdev); + default: + return -ENOTTY; + } +} + +static ssize_t vfio_ub_rw(struct vfio_ub_core_device *vdev, char __user *buf, size_t count, + loff_t *ppos, bool iswrite) +{ + unsigned int index = VFIO_UB_OFFSET_TO_INDEX(*ppos); + + if (index >= (VFIO_UB_NUM_REGIONS + vdev->num_regions)) + return -EINVAL; + + switch (index) { + case VFIO_UB_CONFIG_REGION_INDEX: + return vfio_ub_config_rw(vdev, buf, count, ppos, iswrite); + case VFIO_UB_REGION0_INDEX: + case VFIO_UB_REGION1_INDEX: + case VFIO_UB_REGION2_INDEX: + return vfio_ub_res_rw(vdev, buf, count, ppos, iswrite); + default: + return -EINVAL; + } +} + +ssize_t vfio_ub_core_read(struct vfio_device *core_vdev, char __user *buf, size_t count, + loff_t *ppos) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + + if (!count) + return 0; + + return vfio_ub_rw(vdev, buf, count, ppos, false); +} + +ssize_t vfio_ub_core_write(struct vfio_device *core_vdev, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + + if (!count) + return 0; + + return vfio_ub_rw(vdev, (char __user *)buf, count, ppos, true); +} + +int vfio_ub_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + u64 phys_len, req_len, pgoff, req_start; + struct ub_entity *uent = vdev->uent; + unsigned int index; + + if (vma->vm_end < vma->vm_start) + return -EINVAL; + + /* + * VM_SHARED means user's changing to mmio can immediately arrive ub device, + * or it is unspecified. + */ + if ((vma->vm_flags & VM_SHARED) == 0) + return -EINVAL; + + index = vma->vm_pgoff >> (VFIO_UB_OFFSET_SHIFT - PAGE_SHIFT); + + if (index > VFIO_UB_REGION2_INDEX) + return -EINVAL; + + phys_len = PAGE_ALIGN(ub_resource_len(uent, index)); + req_len = vma->vm_end - vma->vm_start; + pgoff = vma->vm_pgoff & ((1U << (VFIO_UB_OFFSET_SHIFT - PAGE_SHIFT)) - 1); + req_start = pgoff << PAGE_SHIFT; + + if (req_start + req_len > phys_len) + return -EINVAL; + + vm_flags_set(vma, VM_IO | VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP | + VM_PFNMAP | VM_WIPEONFORK); + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_pgoff = (ub_resource_start(uent, index) >> PAGE_SHIFT) + pgoff; + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +int vfio_ub_core_init_dev(struct vfio_device *core_vdev) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + + vdev->uent = to_ub_entity(core_vdev->dev); + vdev->irq_type = VFIO_UB_NUM_IRQS; + mutex_init(&vdev->igate); + return 0; +} + +void vfio_ub_core_release_dev(struct vfio_device *core_vdev) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + + mutex_destroy(&vdev->igate); +} + +void vfio_ub_core_request(struct vfio_device *core_vdev, unsigned int count) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + + mutex_lock(&vdev->igate); + + if (vdev->req_trigger) { + if (!(count % 10)) + dev_notice_ratelimited(&vdev->uent->dev, + "Relaying device request to user #%u\n", count); + eventfd_signal(vdev->req_trigger, 1); + } else if (count == 0) { + ub_warn(vdev->uent, + "No device request channel registered, blocked until released by user\n"); + } + + mutex_unlock(&vdev->igate); +} + +void vfio_ub_iommufd_physical_unbind(struct vfio_device *core_vdev) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + + vfio_iommufd_physical_unbind(core_vdev); + + /* Now, valid tid are allocated at attach_ioas interface, + * but tid free at this unbind interface. + * detach_ioas interface are not called at vfio_compt mode. + */ + ub_unset_user_info(vdev->uent); +} + +void vfio_ub_core_disable_all(struct vfio_ub_core_device *vdev) +{ + struct ub_entity *uent = vdev->uent; + + if (!list_empty(&uent->ue_list)) + ub_disable_entities(uent); +} + +int vfio_ub_iommufd_physical_attach_ioas(struct vfio_device *core_vdev, u32 *pt_id) +{ + struct vfio_ub_core_device *vdev = + container_of(core_vdev, struct vfio_ub_core_device, vdev); + int ret; + + ret = vfio_iommufd_physical_attach_ioas(core_vdev, pt_id); + if (!ret) + ub_set_user_info(vdev->uent); + + return ret; +} + +void vfio_ub_iommufd_physical_detach_ioas(struct vfio_device *core_vdev) +{ + vfio_iommufd_physical_detach_ioas(core_vdev); +} diff --git a/drivers/vfio/ubus/vfio_ub_intrs.c b/drivers/vfio/ubus/vfio_ub_intrs.c new file mode 100644 index 0000000000000000000000000000000000000000..8dfc13c39e45af47a0e8fd7e8e61fccee2731a88 --- /dev/null +++ b/drivers/vfio/ubus/vfio_ub_intrs.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + * + * Thanks to Alex Williamson and Tom Lyon for their original + * vfio implementation. + * + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "vfio ub: " fmt + +#include +#include +#include +#include +#include + +#include "vfio_ub_private.h" + +static const char *ub_get_name(const struct ub_entity *uent) +{ + return dev_name(&uent->dev); +} + +static irqreturn_t vfio_ub_intr_handler(int irq, void *arg) +{ + struct eventfd_ctx *trigger = (struct eventfd_ctx *)arg; + + eventfd_signal(trigger, 1); + return IRQ_HANDLED; +} + +static int vfio_ub_intr_tid_update(struct vfio_ub_core_device *vdev, int vector) +{ + void __iomem *vaddr; + u32 tid, addr_num; + u16 addr_idx; + int ret; + + vaddr = ub_iomap(vdev->uent, 0, 0); + if (!vaddr) + return -ENOMEM; + + ret = ub_cfg_read_dword(vdev->uent, UB_ENTITY_TOKEN_ID, &tid); + if (ret) { + ub_iounmap(vaddr); + return ret; + } + + addr_num = ub_intr_addr_count(vdev->uent); + addr_idx = readl(vaddr + vdev->usi_vector_offset + + vector * UB_INTR_VECTOR_ENTRY_SIZE + + UB_INTR_VECTOR_ADDR_INDEX) & GENMASK(15, 0); + if (addr_idx >= addr_num) { + ub_iounmap(vaddr); + ub_err(vdev->uent, "invalid usi addr table info, addr_idx=%#x, addr_num=%#x\n", + addr_idx, addr_num); + return -EFAULT; + } + + writel(0, vaddr + vdev->usi_addr_offset + addr_idx * UB_INTR_ADDR_ENTRY_SIZE + + UB_INTR_ADDR_TOKENID); + writel((tid & UB_INTR_ADDR_TOKENID_MASK) | (0x1 << UB_INTR_ADDR_VALID_BIT), + vaddr + vdev->usi_addr_offset + addr_idx * UB_INTR_ADDR_ENTRY_SIZE + + UB_INTR_ADDR_TOKENID); + + ub_iounmap(vaddr); + return 0; +} + +static int vfio_ub_intr_set_vector_signal(struct vfio_ub_core_device *vdev, + int vector, int eventfd) +{ + struct ub_entity *uent = vdev->uent; + struct eventfd_ctx *trigger; + int irq_vec, ret; + + irq_vec = ub_irq_vector(uent, vector); + + /* if already assigned interrupts, just free it and reassigned it later. */ + if (vdev->ctx[vector].trigger != NULL) { + irq_bypass_unregister_producer(&vdev->ctx[vector].producer); + free_irq(irq_vec, vdev->ctx[vector].trigger); + kfree(vdev->ctx[vector].name); + eventfd_ctx_put(vdev->ctx[vector].trigger); + vdev->ctx[vector].trigger = NULL; + } + + if (eventfd < 0) + return 0; + + vdev->ctx[vector].name = kasprintf(GFP_KERNEL, "vfio-ub-intr[%d](%s)", + vector, ub_get_name(uent)); + if (!vdev->ctx[vector].name) + return -ENOMEM; + + trigger = eventfd_ctx_fdget(eventfd); + if (IS_ERR(trigger)) { + ret = PTR_ERR(trigger); + goto err_kasprintf; + } + + ret = request_irq(irq_vec, vfio_ub_intr_handler, 0, + vdev->ctx[vector].name, trigger); + if (ret) + goto err_eventfd; + + ret = vfio_ub_intr_tid_update(vdev, vector); + if (ret) + goto err_irq; + + vdev->ctx[vector].producer.irq = irq_vec; + vdev->ctx[vector].producer.token = trigger; + ret = irq_bypass_register_producer(&vdev->ctx[vector].producer); + if (unlikely(ret)) { + ub_info(uent, + "irq bypass producer registration failed, ret=%d\n", ret); + vdev->ctx[vector].producer.token = NULL; + } + vdev->ctx[vector].trigger = trigger; + + return 0; + +err_irq: + free_irq(irq_vec, trigger); +err_eventfd: + eventfd_ctx_put(trigger); +err_kasprintf: + kfree(vdev->ctx[vector].name); + vdev->ctx[vector].name = NULL; + return ret; +} + +static int vfio_ub_intr_set_block(struct vfio_ub_core_device *vdev, unsigned int start, + unsigned int count, int32_t *fds) +{ + int i, j, ret = 0; + + if ((start >= vdev->num_ctx) || ((start + count) > vdev->num_ctx)) + return -EINVAL; + + for (i = 0, j = start; i < count; i++, j++) { + int fd = fds ? fds[i] : -1; + + ret = vfio_ub_intr_set_vector_signal(vdev, j, fd); + if (ret) + break; + } + + if (ret) { + for (--j; j >= (int)start; j--) + vfio_ub_intr_set_vector_signal(vdev, j, -1); + } + + return ret; +} + +static void vfio_ub_intr_disable(struct vfio_ub_core_device *vdev) +{ + struct ub_entity *uent = vdev->uent; + + vfio_ub_intr_set_block(vdev, 0, vdev->num_ctx, NULL); + + ub_disable_intr(uent); + + vdev->irq_type = VFIO_UB_NUM_IRQS; + vdev->num_ctx = 0; + kfree(vdev->ctx); + vdev->ctx = NULL; +} + +static int vfio_ub_intr_enable(struct vfio_ub_core_device *vdev, int nvec) +{ + struct ub_entity *uent = vdev->uent; + int ret; + + vdev->ctx = kcalloc(nvec, sizeof(struct vfio_ub_irq_ctx), GFP_KERNEL); + if (!vdev->ctx) + return -ENOMEM; + + ret = ub_alloc_irq_vectors(uent, 1, nvec); + if (ret < nvec) { + if (ret > 0) + ub_disable_intr(uent); + kfree(vdev->ctx); + vdev->ctx = NULL; + return ret; + } + + vdev->num_ctx = nvec; + vdev->irq_type = VFIO_UB_INTR_IRQ_INDEX; + + return 0; +} + +static int vfio_ub_intr_irq_trigger(struct vfio_ub_core_device *vdev, + unsigned int index, unsigned int start, + unsigned int count, uint32_t flags, void *data) +{ + int ret; + unsigned int i; + + if (irq_is(vdev, index) && !count && (flags & VFIO_IRQ_SET_DATA_NONE)) { + vfio_ub_intr_disable(vdev); + return 0; + } + + /* DATA_EVENTFD with ACTION_TRIGGER means set irqs */ + if (flags & VFIO_IRQ_SET_DATA_EVENTFD) { + int32_t *fds = (int32_t *)data; + + /* if ub device irq has setted, then update irq eventfd handler */ + if (irq_is(vdev, index)) + return vfio_ub_intr_set_block(vdev, start, count, fds); + + ret = vfio_ub_intr_enable(vdev, start + count); + if (ret) + return ret; + + ret = vfio_ub_intr_set_block(vdev, start, count, fds); + if (ret) + vfio_ub_intr_disable(vdev); + + return ret; + } + + if (!irq_is(vdev, index) || ((start + count) > vdev->num_ctx)) + return -EINVAL; + + for (i = start; i < (start + count); i++) { + if (!vdev->ctx[i].trigger) + continue; + /* DATA_NONE with ACTION_TRIGGER means trigger all irqs */ + if (flags & VFIO_IRQ_SET_DATA_NONE) { + eventfd_signal(vdev->ctx[i].trigger, 1); + } else if (flags & VFIO_IRQ_SET_DATA_BOOL) { + /* DATA_BOOL with ACTION_TRIGGER means trigger sparse irqs */ + uint8_t *bools = (uint8_t *)data; + + if (bools[i - start]) + eventfd_signal(vdev->ctx[i].trigger, 1); + } + } + + return 0; +} + +static int vfio_ub_set_ctx_trigger_single(struct eventfd_ctx **ctx_pptr, + unsigned int num, uint32_t flags, + void *data) +{ + struct eventfd_ctx *efdctx; + int32_t file_desc; + + if (flags & VFIO_IRQ_SET_DATA_EVENTFD) { + if (!num) + return -EINVAL; + + file_desc = *(int32_t *)data; + if (file_desc == -1) { + if (*ctx_pptr) + eventfd_ctx_put(*ctx_pptr); + *ctx_pptr = NULL; + } else if (file_desc >= 0) { + efdctx = eventfd_ctx_fdget(file_desc); + if (IS_ERR(efdctx)) + return PTR_ERR(efdctx); + + if (*ctx_pptr) + eventfd_ctx_put(*ctx_pptr); + + *ctx_pptr = efdctx; + } + return 0; + } + + return -EINVAL; +} + +static int vfio_ub_set_req_trigger(struct vfio_ub_core_device *vdev, + unsigned int index, unsigned int start, + unsigned int count, uint32_t flags, void *data) +{ + if (index != VFIO_UB_REQ_IRQ_INDEX || start != 0 || count > 1) + return -EINVAL; + + return vfio_ub_set_ctx_trigger_single(&vdev->req_trigger, count, flags, data); +} + +int vfio_ub_set_irqs_ioctl(struct vfio_ub_core_device *vdev, uint32_t flags, + unsigned int index, unsigned int start, + unsigned int count, void *data) +{ + int ret = 0; + + switch (index) { + case VFIO_UB_INTR_IRQ_INDEX: + if (flags & VFIO_IRQ_SET_ACTION_TRIGGER) + ret = vfio_ub_intr_irq_trigger(vdev, index, start, + count, flags, data); + break; + case VFIO_UB_REQ_IRQ_INDEX: + if (flags & VFIO_IRQ_SET_ACTION_TRIGGER) + ret = vfio_ub_set_req_trigger(vdev, index, start, + count, flags, data); + break; + default: + ret = -ENOTTY; + break; + } + + return ret; +} diff --git a/drivers/vfio/ubus/vfio_ub_private.h b/drivers/vfio/ubus/vfio_ub_private.h new file mode 100644 index 0000000000000000000000000000000000000000..dada03612cf593302af51e9a908cde3eb54cf2f2 --- /dev/null +++ b/drivers/vfio/ubus/vfio_ub_private.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + * + * Thanks to Alex Williamson and Tom Lyon for their original + * vfio implementation. + * + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef __VFIO_UB_PRIVATE_H__ +#define __VFIO_UB_PRIVATE_H__ + +#include +#include +#include + +#define UB_CFG0_MAX_CAP 255 +#define UB_CFG1_MAX_CAP 511 /* cfg1 cap start from 256 */ + +struct vfio_ub_irq_ctx { + struct eventfd_ctx *trigger; + char *name; + bool masked; + struct irq_bypass_producer producer; +}; + +struct vfio_ub_core_device; +struct perm_bits { + u8 *virt; + u8 *write; + u8 *entnro; + u8 *ent0eo; + u8 *exist; + int (*readfn)(struct vfio_ub_core_device *vdev, u64 pos, int count, + __le32 *val); + int (*writefn)(struct vfio_ub_core_device *vdev, u64 pos, int count, + __le32 val); +}; + +struct vfio_ub_slice { + u8 *cfg; + u32 cap_id; + int used_size; +}; + +struct vfio_ub_config { + struct vfio_ub_slice *slice; + int nums; + u8 final_slice_supported[UB_CFG1_MAX_CAP + 1]; /* do not incude port basic && port cap */ + int map[UB_CFG1_MAX_CAP + 1]; +}; + +struct vfio_ub_core_device { + struct vfio_device vdev; + struct ub_entity *uent; + struct vfio_ub_config vconfig; + void __iomem *resmap[MAX_UB_RES_NUM]; + int num_ctx; /* num of enabled irqs */ + int irq_type; /* interrupt type of this uent */ + struct vfio_ub_irq_ctx *ctx; + int num_regions; /* vfio-ub additional regions */ + int num_ext_irqs; /* vfio-ub additional extended irqs */ + int num_vendor_regions; /* vfio-ub additional vendor-defined regions */ + int num_vendor_irqs; /* vfio-ub additional vendor-defined irqs */ + bool reset_works; /* vfio-ub support reset flag */ + u64 usi_vector_offset; + u32 usi_vector_size; + u64 usi_addr_offset; + u32 usi_addr_size; + struct eventfd_ctx *req_trigger; + struct mutex igate; +}; + +#define VFIO_UB_OFFSET_SHIFT 40 +#define VFIO_UB_OFFSET_TO_INDEX(off) ((off) >> VFIO_UB_OFFSET_SHIFT) +#define VFIO_UB_INDEX_TO_OFFSET(index) ((u64)(index) << VFIO_UB_OFFSET_SHIFT) +#define VFIO_UB_OFFSET_MASK (((u64)(1) << VFIO_UB_OFFSET_SHIFT) - 1) + +#define irq_is(vdev, type) ((vdev)->irq_type == (type)) +#define BYTE_SIZE 1 +#define WORD_SIZE 2 +#define DWORD_SIZE 4 +#define DWORD_BITS 32 +#define BYTE_BITS 8 + +int vfio_ub_init_perm_bits(void); +void vfio_ub_uninit_perm_bits(void); +int vfio_ub_config_init(struct vfio_ub_core_device *vdev); +void vfio_ub_config_uninit(struct vfio_ub_core_device *vdev); +ssize_t vfio_ub_config_rw(struct vfio_ub_core_device *vdev, char __user *buf, + size_t count, loff_t *ppos, bool iswrite); +ssize_t vfio_ub_res_rw(struct vfio_ub_core_device *vdev, char __user *buf, + size_t count, loff_t *ppos, bool iswrite); +int vfio_ub_set_irqs_ioctl(struct vfio_ub_core_device *vdev, uint32_t flags, + unsigned int index, unsigned int start, + unsigned int count, void *data); +int vfio_ub_core_register_device(struct vfio_ub_core_device *vdev); +void vfio_ub_core_unregister_device(struct vfio_ub_core_device *vdev); +int vfio_ub_core_open_device(struct vfio_device *core_vdev); +void vfio_ub_core_close_device(struct vfio_device *core_vdev); +long vfio_ub_core_ioctl(struct vfio_device *core_vdev, unsigned int cmd, unsigned long arg); +ssize_t vfio_ub_core_write(struct vfio_device *core_vdev, const char __user *buf, + size_t count, loff_t *ppos); +ssize_t vfio_ub_core_read(struct vfio_device *core_vdev, char __user *buf, size_t count, + loff_t *ppos); +int vfio_ub_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma); +int vfio_ub_core_init_dev(struct vfio_device *core_vdev); +void vfio_ub_core_release_dev(struct vfio_device *core_dev); +void vfio_ub_core_request(struct vfio_device *core_vdev, unsigned int count); +void vfio_ub_core_disable_all(struct vfio_ub_core_device *vdev); +int vfio_ub_iommufd_physical_attach_ioas(struct vfio_device *vdev, u32 *pt_id); +void vfio_ub_iommufd_physical_detach_ioas(struct vfio_device *vdev); +void vfio_ub_iommufd_physical_unbind(struct vfio_device *core_vdev); +void vfio_ub_unset_resmap(struct vfio_ub_core_device *vdev); + +#endif /* __VFIO_UB_PRIVATE_H__ */ diff --git a/drivers/vfio/ubus/vfio_ub_rdwr.c b/drivers/vfio/ubus/vfio_ub_rdwr.c new file mode 100644 index 0000000000000000000000000000000000000000..3fc8082e22f7337892057449eacbb935fb649b89 --- /dev/null +++ b/drivers/vfio/ubus/vfio_ub_rdwr.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + * + * Thanks to Alex Williamson and Tom Lyon for their original + * vfio implementation. + * + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#define pr_fmt(fmt) "vfio ub: " fmt + +#include +#include + +#include "vfio_ub_private.h" + +#ifdef __LITTLE_ENDIAN +#define vfio_mmio_read32 ioread32 +#define vfio_mmio_write32 iowrite32 +#define vfio_mmio_read16 ioread16 +#define vfio_mmio_write16 iowrite16 +#else +#define vfio_mmio_read32 ioread32be +#define vfio_mmio_write32 iowrite32be +#define vfio_mmio_read16 ioread16be +#define vfio_mmio_write16 iowrite16be +#endif +#define vfio_mmio_read8 ioread8 +#define vfio_mmio_write8 iowrite8 + +#define VFIO_UB_IOWRITE(size) \ +static void vfio_ub_iowrite##size(struct vfio_ub_core_device *vdev, \ + u##size val, void __iomem *io) \ +{ \ + vfio_mmio_write##size(val, io); \ +} + +VFIO_UB_IOWRITE(8) +VFIO_UB_IOWRITE(16) +VFIO_UB_IOWRITE(32) + +#define VFIO_UB_IOREAD(size) \ +static void vfio_ub_ioread##size(struct vfio_ub_core_device *vdev, \ + u##size * val, void __iomem *io) \ +{ \ + *val = vfio_mmio_read##size(io); \ +} + +VFIO_UB_IOREAD(8) +VFIO_UB_IOREAD(16) +VFIO_UB_IOREAD(32) + +static int vfio_ub_setup_resmap(struct vfio_ub_core_device *vdev, int res) +{ + struct ub_entity *uent = vdev->uent; + void __iomem *io_addr; + + if (vdev->resmap[res]) + return 0; + + io_addr = ub_iomap(uent, res, 0); + if (!io_addr) + return -ENOMEM; + + vdev->resmap[res] = io_addr; + + return 0; +} + +void vfio_ub_unset_resmap(struct vfio_ub_core_device *vdev) +{ + int res; + + for (res = 0; res < MAX_UB_RES_NUM; res++) { + if (!vdev->resmap[res]) + continue; + + ub_iounmap(vdev->resmap[res]); + vdev->resmap[res] = NULL; + } +} + +static int do_io_rw32(struct vfio_ub_core_device *vdev, char __user *buf, + void __iomem *io, loff_t off, bool iswrite) +{ + u32 val; + + if (iswrite) { + if (copy_from_user(&val, buf, DWORD_SIZE)) + return -EFAULT; + + vfio_ub_iowrite32(vdev, val, io + off); + } else { + vfio_ub_ioread32(vdev, &val, io + off); + if (copy_to_user(buf, &val, DWORD_SIZE)) + return -EFAULT; + } + + return 0; +} + +static int do_io_rw16(struct vfio_ub_core_device *vdev, char __user *buf, + void __iomem *io, loff_t off, bool iswrite) +{ + u16 val; + + if (iswrite) { + if (copy_from_user(&val, buf, WORD_SIZE)) + return -EFAULT; + + vfio_ub_iowrite16(vdev, val, io + off); + } else { + vfio_ub_ioread16(vdev, &val, io + off); + if (copy_to_user(buf, &val, WORD_SIZE)) + return -EFAULT; + } + + return 0; +} + +static int do_io_rw8(struct vfio_ub_core_device *vdev, char __user *buf, + void __iomem *io, loff_t off, bool iswrite) +{ + u8 val; + + if (iswrite) { + if (copy_from_user(&val, buf, BYTE_SIZE)) + return -EFAULT; + + vfio_ub_iowrite8(vdev, val, io + off); + } else { + vfio_ub_ioread8(vdev, &val, io + off); + if (copy_to_user(buf, &val, BYTE_SIZE)) + return -EFAULT; + } + + return 0; +} + +static size_t cal_io_rw_cnt(struct vfio_ub_core_device *vdev, int res, loff_t off, size_t count) +{ + size_t cnt = 0; + u64 left_start; + u32 left_size; + u64 right_start; + u32 right_size; + + /* Now usi info was installed at resource0 */ + if (res != VFIO_UB_REGION0_INDEX) + return count; + + if (vdev->usi_vector_offset < vdev->usi_addr_offset) { + left_start = vdev->usi_vector_offset; + left_size = vdev->usi_vector_size; + right_start = vdev->usi_addr_offset; + right_size = vdev->usi_addr_size; + } else { + left_start = vdev->usi_addr_offset; + left_size = vdev->usi_addr_size; + right_start = vdev->usi_vector_offset; + right_size = vdev->usi_vector_size; + } + + if (off < left_start) + cnt = min(count, (size_t)(left_start - off)); + else if (off >= left_start + left_size && off < right_start) + cnt = min(count, (size_t)(right_start - off)); + else if (off >= right_start + right_size) + cnt = count; + + return cnt; +} + +static ssize_t do_io_rw(struct vfio_ub_core_device *vdev, void __iomem *io, + char __user *buf, int res, loff_t off, size_t count, bool iswrite) +{ + ssize_t done = 0; + u64 tmp_addr; + u64 tmp_vec; + int ret; + + while (count) { + size_t fillable, filled; + + fillable = cal_io_rw_cnt(vdev, res, off, count); + if (fillable >= DWORD_SIZE && !(off % DWORD_SIZE)) { + ret = do_io_rw32(vdev, buf, io, off, iswrite); + if (ret) + return ret; + + filled = DWORD_SIZE; + } else if (fillable >= WORD_SIZE && !(off % WORD_SIZE)) { + ret = do_io_rw16(vdev, buf, io, off, iswrite); + if (ret) + return ret; + + filled = WORD_SIZE; + } else if (fillable) { + ret = do_io_rw8(vdev, buf, io, off, iswrite); + if (ret) + return ret; + + filled = BYTE_SIZE; + } else { + tmp_vec = vdev->usi_vector_offset + vdev->usi_vector_size; + tmp_addr = vdev->usi_addr_offset + vdev->usi_addr_size; + if (off >= vdev->usi_vector_offset && off < tmp_vec) + filled = min(count, (size_t)(tmp_vec - off)); + else if (off >= vdev->usi_addr_offset && off < tmp_addr) + filled = min(count, (size_t)(tmp_addr - off)); + else + return -EFAULT; + + if (!iswrite) { + u8 val = 0xff; + + for (size_t i = 0; i < filled; i++) { + if (copy_to_user(buf + i, &val, 1)) + return -EFAULT; + } + } + } + + count -= filled; + done += filled; + off += filled; + buf += filled; + } + + return done; +} + +ssize_t vfio_ub_res_rw(struct vfio_ub_core_device *vdev, char __user *buf, + size_t count, loff_t *ppos, bool iswrite) +{ + struct ub_entity *uent = vdev->uent; + loff_t pos = *ppos & VFIO_UB_OFFSET_MASK; + int res = VFIO_UB_OFFSET_TO_INDEX(*ppos); + resource_size_t end; + void __iomem *io_addr; + int ret; + ssize_t done; + + if (ub_resource_start(uent, res)) + end = ub_resource_len(uent, res); + else + return -EINVAL; + + if (pos >= end) + return -EINVAL; + + count = min(count, (size_t)(end - pos)); + + ret = vfio_ub_setup_resmap(vdev, res); + if (ret) + return ret; + + io_addr = vdev->resmap[res]; + + done = do_io_rw(vdev, io_addr, buf, res, pos, count, iswrite); + if (done > 0) + *ppos += done; + + return done; +} diff --git a/include/linux/cper.h b/include/linux/cper.h index f78295bd5d18e6d20bd992de1a50711628446756..2ff63ec2d3948280fabf93c30e0733cd4924d04a 100644 --- a/include/linux/cper.h +++ b/include/linux/cper.h @@ -220,6 +220,10 @@ enum { #define CPER_SEC_DMAR_IOMMU \ GUID_INIT(0x036F84E1, 0x7F37, 0x428c, 0xA7, 0x9E, 0x57, 0x5F, \ 0xDF, 0xAA, 0x84, 0xEC) +/* UBUS */ +#define CPER_SEC_UBUS \ + GUID_INIT(0x74D255B0, 0x73E8, 0x488A, 0xB8, 0x67, 0x90, 0x65, \ + 0x4A, 0x35, 0x86, 0x5E) #define CPER_PROC_VALID_TYPE 0x0001 #define CPER_PROC_VALID_ISA 0x0002 @@ -564,6 +568,20 @@ struct cper_sec_pcie { u8 aer_info[96]; }; +struct cper_sec_ubus { + u64 validation_bits; + u32 eid; + u32 cna; + u32 port; + u32 vendor; + u16 device; + u16 type; + u16 class_code; + u16 reserved; + u32 overflow_flag; + u32 err_info[72]; +}; + /* Firmware Error Record Reference, UEFI v2.7 sec N.2.10 */ struct cper_sec_fw_err_rec_ref { u8 record_type; diff --git a/include/linux/irqdomain_defs.h b/include/linux/irqdomain_defs.h index 5c1fe6f1fcde86e85c3ff2721d4a0096c0d8913e..1780c87e75ebe9212a118e3f039a14dd99aa1a87 100644 --- a/include/linux/irqdomain_defs.h +++ b/include/linux/irqdomain_defs.h @@ -2,6 +2,8 @@ #ifndef _LINUX_IRQDOMAIN_DEFS_H #define _LINUX_IRQDOMAIN_DEFS_H +#include + /* * Should several domains have the same device node, but serve * different purposes (for example one domain is for PCI/MSI, and the @@ -28,6 +30,7 @@ enum irq_domain_bus_token { DOMAIN_BUS_PCI_DEVICE_IMS, DOMAIN_BUS_DEVICE_MSI, DOMAIN_BUS_WIRED_TO_MSI, + CK_KABI_EXTEND_ENUM(DOMAIN_BUS_UB_MSI) }; #endif /* _LINUX_IRQDOMAIN_DEFS_H */ diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 0f51bc24ae5952e25941e2df1ebd73ed0173e8eb..44a9dd285997ff76a34f3f17ea4837efbad76036 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -948,4 +948,30 @@ struct cdx_device_id { __u32 override_only; }; +#define UB_ANY_ID (~0) + +enum { + UB_ID_F_VFIO_DRIVER_OVERRIDE = 1, +}; + +/** + * struct ub_device_id - UB device identifier + * @vendor: Vendor ID + * @device: Device ID + * @mod_vendor: Module Vendor ID + * @module: Module ID + * @class_code: Device class base code and sub code to match. See + * include/ub/ubus/ubus_ids.h for a full list of classes. + * @class_mask: Limit which sub-fields of the class code field are + * compared. + * @override_only: Match only when dev->driver_override is this driver. + */ +struct ub_device_id { + __u32 vendor, device; /* Vendor and device ID or UB_ANY_ID*/ + __u32 mod_vendor, module; /* Module ID's or UB_ANY_ID */ + __u16 class_code, class_mask; /* Base code and sub code */ + unsigned long driver_data; /* Data private to the driver */ + __u32 override_only; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/msi.h b/include/linux/msi.h index 7d0e717e2bdec1d0ccb064135f5c68ef641c910e..578fe61c65231cf99e56468ed475546aff07f23f 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -159,6 +159,19 @@ struct msi_desc_data { #define MSI_MAX_INDEX ((unsigned int)USHRT_MAX) +struct ub_intr_desc { + struct { + u32 mask; + u8 is_type1 : 1; + u16 entry_nr; + u16 addr_index; + } intr_attrib; + u16 addr_num; + u16 vec_num; + void __iomem *vector_base; + void __iomem *addr_base; +}; + /** * struct msi_desc - Descriptor structure for MSI based interrupts * @irq: The base interrupt number @@ -202,6 +215,11 @@ struct msi_desc { union { struct pci_msi_desc pci; struct msi_desc_data data; +#ifndef __GENKSYMS__ + struct ub_intr_desc ub_intr; +#else + KABI_EXTEND_WITH_SIZE(KABI_RESERVE(1), 5) +#endif }; CK_KABI_RESERVE(1) @@ -578,6 +596,7 @@ enum { MSI_FLAG_PCI_MSIX_ALLOC_DYN = (1 << 20), /* Support for PCI/IMS */ MSI_FLAG_PCI_IMS = (1 << 21), + CK_KABI_EXTEND_ENUM(MSI_FLAG_UB_INTR = (1 << 22)) }; /** @@ -691,4 +710,31 @@ static inline struct irq_domain *pci_msi_get_device_domain(struct pci_dev *pdev) static inline void pci_write_msi_msg(unsigned int irq, struct msi_msg *msg) { } #endif /* !CONFIG_PCI_MSI */ +struct ub_usi_entry { + u32 vector; /* Kernel uses to write allocated vector */ + u16 entry; /* Driver uses to specify entry, OS writes */ +}; +struct ub_entity; +struct irq_affinity; +#ifdef CONFIG_UB_UBUS_USI +struct ub_entity *msi_desc_to_ub_entity(struct msi_desc *desc); +void ub_msi_mask_irq(struct irq_data *data); +void ub_msi_unmask_irq(struct irq_data *data); +void ub_write_interruptid(struct ub_entity *uent); +struct irq_domain *ub_msi_create_irq_domain(struct fwnode_handle *fwnode, + struct msi_domain_info *info, + struct irq_domain *parent); +void __ub_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg); +int ub_interrupt_id_alloc(struct ub_entity *uent); +void ub_interrupt_id_free(struct ub_entity *uent); +bool ub_setup_usi_device_domain(struct ub_entity *uent, unsigned int hwsize); +void __iomem *ub_vector_desc_addr(struct msi_desc *desc); +u32 ub_intr_addr_count(struct ub_entity *uent); +u32 ub_intr_vec_count(struct ub_entity *uent); +int usi_setup_interrupts(struct ub_entity *uent, + struct ub_usi_entry *entries, int nvec, + struct irq_affinity *affd); +int ub_setup_msi_context(struct ub_entity *uent); +#endif /* CONFIG_UB_UBUS_USI */ + #endif /* LINUX_MSI_H */ diff --git a/include/net/ub/ubl.h b/include/net/ub/ubl.h new file mode 100644 index 0000000000000000000000000000000000000000..63582e147ab774cc646b8c6edca4826d6a43de04 --- /dev/null +++ b/include/net/ub/ubl.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (c) 2023-2023 Hisilicon Limited. + * + * 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 _NET_UB_UBL_H_ +#define _NET_UB_UBL_H_ + +#include +#include +#include +#include + +#define UBL_MOD_VERSION "1.0.0" + +#define UBL_HARD_HLEN 4 +#define UBL_HLEN 7 +#define UBL_ALEN 16 + +#define UBL_LCRC_LEN 2 +#define UBL_BCRC_LEN 4 +#define UBL_LINK_LEN 2 + +#define UB_DEFAULT_NPI 1 + +#define UB_MAX_BLOCK_NUM 16 +#define UB_FLIT_NUM_OF_BLOCK 32 +#define UB_BYTE_OF_FLIT 20 +#define UB_BYTE_OF_BLK (UB_BYTE_OF_FLIT * UB_FLIT_NUM_OF_BLOCK) + +#define UB_BLOCK_CRC_LEN (UBL_BCRC_LEN + UBL_LCRC_LEN) +#define UB_MAX_MTU_PER_BLK (UB_BYTE_OF_BLK - UBL_LINK_LEN - UB_BLOCK_CRC_LEN) +#define UB_DATA_LEN 1500 +#define UB_MAX_MTU ((UB_MAX_BLOCK_NUM * UB_MAX_MTU_PER_BLK) - \ + UBL_HLEN - UBL_HARD_HLEN + UBL_LINK_LEN) +#define UB_MIN_MTU 68 + +#define UB_IPV4_CFG_TYPE 3 +#define UB_IPV6_CFG_TYPE 4 +#define UB_NOIP_CFG_TYPE 5 +#define UB_UNKNOWN_CFG_TYPE 255 + +/** + * struct ublhdr - ub link header + * @cfg: network type configuration + * @resv: reserved bit-field + * @h_cc: congestion control + * @h_npi: network partition identifier + */ +struct ublhdr { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 cfg : 4; + u8 resv : 4; +#else + u8 resv : 4; + u8 cfg : 4; +#endif + __be16 h_cc; + __be32 h_npi; +} __packed; + +/** + * ubl_rmv_sw_ctype - delete software packet type for skb->data + * @skb: buffer to alter + * + * Before the packet is sent to the hardware, remove sw_ctype field + * and restore the original packet. + */ +static inline void *ubl_rmv_sw_ctype(struct sk_buff *skb) +{ + return pskb_pull(skb, sizeof(u8)); +} + +void ubl_setup(struct net_device *dev); +__be16 ubl_type_trans(struct sk_buff *skb, struct net_device *dev, u8 type); +struct net_device *alloc_ubldev_mqs(int sizeof_priv, unsigned int txqs, + unsigned int rxqs); +#define alloc_ubldev_mq(sizeof_priv, count) \ + alloc_ubldev_mqs((sizeof_priv), (count), (count)) + +#endif diff --git a/include/uapi/linux/if_arp.h b/include/uapi/linux/if_arp.h index 4783af9fe5200225044b221cd4499359900669cd..ed514ed01268df24fe960ce7b9fb81493e624a25 100644 --- a/include/uapi/linux/if_arp.h +++ b/include/uapi/linux/if_arp.h @@ -43,6 +43,8 @@ #define ARPHRD_EUI64 27 /* EUI-64 */ #define ARPHRD_INFINIBAND 32 /* InfiniBand */ +#define ARPHRD_UB 38 /* Unified bus */ + /* Dummy types for non ARP hardware */ #define ARPHRD_SLIP 256 #define ARPHRD_CSLIP 257 diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index 69e0457eb2000dfda7b96fd4b1daff93f7970f9f..065ca4e2d3c4b25c122e2b5e1c4962ed524a72a9 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -159,6 +159,7 @@ #define ETH_P_MCTP 0x00FA /* Management component transport * protocol packets */ +#define ETH_P_UB 0x0100 /* Network control packet of Unified Bus */ /* * This is an Ethernet frame header. diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index f906875fc31412b214050156d1cde8dfab0712da..18f4892062045e8fa047476d897ac6d4777004d8 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -254,6 +254,7 @@ struct vfio_device_info { #define VFIO_DEVICE_FLAGS_FSL_MC (1 << 6) /* vfio-fsl-mc device */ #define VFIO_DEVICE_FLAGS_CAPS (1 << 7) /* Info supports caps */ #define VFIO_DEVICE_FLAGS_CDX (1 << 8) /* vfio-cdx device */ +#define VFIO_DEVICE_FLAGS_UB (1 << 9) /* vfio-ub device */ __u32 num_regions; /* Max region index + 1 */ __u32 num_irqs; /* Max IRQ index + 1 */ __u32 cap_offset; /* Offset within info struct of first cap */ @@ -687,6 +688,22 @@ enum { VFIO_PCI_NUM_IRQS }; +/* UB irq types */ +enum { + VFIO_UB_INTR_IRQ_INDEX, + VFIO_UB_REQ_IRQ_INDEX, + VFIO_UB_NUM_IRQS +}; + +/* UB regions types */ +enum { + VFIO_UB_REGION0_INDEX = 0, + VFIO_UB_REGION1_INDEX, + VFIO_UB_REGION2_INDEX, + VFIO_UB_CONFIG_REGION_INDEX, + VFIO_UB_NUM_REGIONS +}; + /* * The vfio-ccw bus driver makes use of the following fixed region and * IRQ index mapping. Unimplemented regions return a size of zero. diff --git a/include/uapi/ub/ubus/ubus.h b/include/uapi/ub/ubus/ubus.h new file mode 100644 index 0000000000000000000000000000000000000000..388a4cf043cee0b84d91e19e3b3ce99e48cf9c0c --- /dev/null +++ b/include/uapi/ub/ubus/ubus.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef _UAPI_UB_UBUS_UBUS_H_ +#define _UAPI_UB_UBUS_UBUS_H_ + +#include +#include + +#define UBUS_API_VERSION 0 + +/* Kernel & User level defines for UBUS IOCTLs. */ + +#define UBUS_TYPE ('U') + +/* -------- IOCTLs for UBUS file descriptor (/dev/unified_bus) -------- */ + +/** + * UBUS_GET_API_VERSION - _IO(UBUS_TYPE, 0) + * + * Report the version of the UBUS API. This allows us to bump the entire + * API version should we later need to add or change features in incompatible + * ways. + * Return: UBUS_API_VERSION + * Availability: Always + */ +#define UBUS_IOCTL_GET_API_VERSION _IO(UBUS_TYPE, 0) + +#define GUID_SIZE 16 + +struct ubus_cmd_bi_create { +#define UBUS_INSTANCE_DYNAMIC_SERVER 2 +#define UBUS_INSTANCE_DYNAMIC_CLUSTER 3 + __u8 type; + __u16 upi; + __u32 eid; + __u8 guid[GUID_SIZE]; +}; + +struct ubus_cmd_bi_destroy { + __u8 guid[GUID_SIZE]; +}; + +struct ubus_cmd_bi_bind { + __u8 instance_guid[GUID_SIZE]; + __u8 dev_guid[GUID_SIZE]; +}; + +struct ubus_cmd_bi_unbind { + __u8 instance_guid[GUID_SIZE]; + __u8 dev_guid[GUID_SIZE]; +}; + +#define UBUS_IOCTL_HEADER_SIZE 8 + +enum ubus_bi_cmd { + UBUS_CMD_BI_CREATE = 0, + UBUS_CMD_BI_DESTROY, + UBUS_CMD_BI_BIND, + UBUS_CMD_BI_UNBIND, + UBUS_CMD_BI_NUM +}; + +struct ubus_ioctl_bus_instance { + __u32 argsz; /* size of *union*->structure */ + __u32 sub_cmd; + union { + struct ubus_cmd_bi_create create; + struct ubus_cmd_bi_destroy destroy; + struct ubus_cmd_bi_bind bind; + struct ubus_cmd_bi_unbind unbind; + }; +}; +#define UBUS_IOCTL_BUS_INSTANCE _IOWR(UBUS_TYPE, 1, struct ubus_ioctl_bus_instance) + +#endif /* _UAPI_UB_UBUS_UBUS_H_ */ diff --git a/include/uapi/ub/ubus/ubus_regs.h b/include/uapi/ub/ubus/ubus_regs.h new file mode 100644 index 0000000000000000000000000000000000000000..47847be68e9147987a81a36415e84e28ee41d5e3 --- /dev/null +++ b/include/uapi/ub/ubus/ubus_regs.h @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef _UAPI_UB_UBUS_UBUS_REGS_H_ +#define _UAPI_UB_UBUS_UBUS_REGS_H_ + +enum ub_cfg_cap_id { + UB_CFG0_BASIC_CAP = 0x000, + UB_SHP_CAP = 0x002, + UB_ERR_RECORD_CAP = 0x003, + UB_ERR_INFO_CAP = 0x004, + UB_EMQ_CAP = 0x005, + UB_CFG1_BASIC_CAP = 0x100, + UB_DECODER_CAP = 0x101, + UB_JETTY_CAP = 0x102, + UB_INT_TYPE1_CAP = 0x103, + UB_INT_TYPE2_CAP = 0x104, + UB_MEM_CAP = 0x106, +}; + +enum ub_port_cap_id { + UB_PORT_CAP15_QDLWS = 15, +}; + +#define UB_SLICE_SZ 0x00000400 +#define UB_CFG_BITMAP_SIZE 32 +#define UB_ROUTE_TABLE_SZ 0x40000000 +#define UB_CFG0_SUPPORT_FEATURE_SIZE 16 +#define UB_GUID_SIZE 16 +#define UB_EID_SIZE 16 +#define UB_FM_EID_SIZE 16 +#define UB_CFG1_SUPPORT_FEATURE_SIZE 16 +#define UB_PORT_SLICE_START 0x00080000 +#define UB_ROUTE_TABLE_SLICE_START 0x3c0000000 +#define UB_CFG_SAPCE_SLICE_END 0x400000000 +#define UB_ADDR_TO_POS(a) ((a) << 2) +#define UB_PORT_SLICE_SIZE UB_ADDR_TO_POS(0x10000) + +/* CFG0 BASIC */ +#define UB_CFG0_BASIC_SLICE 0x0 /* 0x0 */ +#define UB_TOTAL_NUMBER_PORT 0x4 /* 0x1 */ +#define UB_TOTAL_NUMBER_ENTITIES 0x6 +#define UB_CFG0_CAP_BITMAP 0x8 /* 0x2 */ +#define UB_FEATURE_SUPPORT_0 0x28 /* 0xA */ +#define UB_SW_SUPPORT BIT(7) +#define UB_CC_SUPPORT BIT(9) +#define UB_GUID 0x38 /* 0xE */ +#define UB_EID_0 0x48 /* 0x12 */ +#define UB_EID_3 0x54 /* 0x15 */ +#define UB_FM_EID_0 0x58 /* 0x16 */ +#define UB_PRIMARY_CNA 0x68 /* 0x1A */ +#define UB_PRIMARY_CNA_MASK GENMASK(23, 0) +#define UB_UPI 0x7c /* 0x1f */ +#define UB_MODULE 0x80 /* 0x20 */ +#define UB_MODULE_ID_MASK GENMASK(15, 0) +#define UB_ENTITY_RST 0x84 /* 0x21 */ +#define UB_ENTITY_RST_BIT 0x1 +#define UB_MTU_CFG 0x8c /* 0x23 */ +#define UB_MTU_CFG_BITS 0x7 +#define UB_CC_EN 0x90 /* 0x24 */ +#define UB_CC_EN_BIT 0x1 +#define UB_TH_EN 0x94 /* 0x25 */ +#define UB_TH_EN_BIT 0x1 +#define UB_FM_CNA 0x98 /* 0x26 */ +#define UB_FM_CNA_MASK GENMASK(23, 0) +#define UB_UEID_0 0x9c /* 0x27 */ +#define UB_UEID_1 0xa0 /* 0x28 */ +#define UB_UEID_2 0xa4 /* 0x29 */ +#define UB_UEID_3 0xa8 /* 0x2A */ +#define UB_UCNA 0xac /* 0x2B */ +#define UB_UCNA_MASK GENMASK(23, 0) + +/* SHP CAP */ +#define UB_SLOT_START UB_ADDR_TO_POS(0x200) +#define UB_SLOT_POS UB_ADDR_TO_POS(0x10) +#define UB_SLOT_NUM UB_ADDR_TO_POS(0x1) +#define UB_SLOT_CAP UB_ADDR_TO_POS(0x2) +#define UB_SLOT_PPS 0x1 +#define UB_SLOT_WLPS 0x2 +#define UB_SLOT_PLPS 0x4 +#define UB_SLOT_PDSS 0x8 +#define UB_SLOT_PWCS 0x10 +#define UB_SLOT_PORT UB_ADDR_TO_POS(0x3) +#define UB_SLOT_START_PORT 0x0000ffff +#define UB_SLOT_PP_CTRL UB_ADDR_TO_POS(0x4) +#define UB_SLOT_PP_CTRL_MASK BIT(0) +#define UB_SLOT_WL_CTRL UB_ADDR_TO_POS(0x5) +#define UB_SLOT_WL_CTRL_MASK GENMASK(1, 0) +#define UB_SLOT_PL_CTRL UB_ADDR_TO_POS(0x6) +#define UB_SLOT_PL_CTRL_MASK GENMASK(1, 0) +#define UB_SLOT_MS_CTRL UB_ADDR_TO_POS(0x7) +#define UB_SLOT_MS_CTRL_MASK BIT(0) +#define UB_SLOT_PD_CTRL UB_ADDR_TO_POS(0x8) +#define UB_SLOT_PD_CTRL_MASK BIT(0) +#define UB_SLOT_PDS_CTRL UB_ADDR_TO_POS(0x9) +#define UB_SLOT_PDS_CTRL_MASK BIT(0) +#define UB_SLOT_PW_CTRL UB_ADDR_TO_POS(0xA) +#define UB_SLOT_PP_STA UB_ADDR_TO_POS(0xB) +#define UB_SLOT_PP_STA_MASK BIT(0) +#define UB_SLOT_PD_STA UB_ADDR_TO_POS(0xC) +#define UB_SLOT_PD_STA_MASK BIT(0) +#define UB_SLOT_PDSC_STA UB_ADDR_TO_POS(0xD) +#define UB_SLOT_PDSC_STA_MASK BIT(0) + +/* CFG1 BASIC */ +#define UB_CFG1_BASIC 0x40000 +#define UB_CFG1_BASIC_SLICE (0x0 + UB_CFG1_BASIC) /* 0x0 */ +#define UB_CFG1_CAP_BITMAP (0x4 + UB_CFG1_BASIC) /* 0x1 */ +#define UB_DECODER_CAP_BIT 0x2 +#define UB_JETTY_CAP_BIT 0x4 +#define UB_INT_TYPE1_CAP_BIT 0x8 +#define UB_INT_TYPE2_CAP_BIT 0x10 +#define UB_MEM_CAP_BIT 0x40 +#define UB_CFG1_SUPPORT_FEATURE_L (0x24 + UB_CFG1_BASIC) /* 0x9 */ +#define UB_VDBS_SUPPORT 0x1 +#define UB_VDSS_SUPPORT 0x2 +#define UB_MGS_SUPPORT 0x4 +#define UB_UBBAS_SUPPORT 0x20 +#define UB_ERS0S_SUPPORT 0x40 +#define UB_ERS1S_SUPPORT 0x80 +#define UB_ERS2S_SUPPORT 0x100 +#define UB_CDMAS_SUPPORT 0x200 +#define UB_DECODER_JURIS 0x400 +#define UB_ERS0_SS (0x34 + UB_CFG1_BASIC) /* 0xD */ +#define UB_ERS1_SS (0x38 + UB_CFG1_BASIC) /* 0xE */ +#define UB_ERS2_SS (0x3c + UB_CFG1_BASIC) /* 0xF */ +#define UB_ERS0_SA_L (0x40 + UB_CFG1_BASIC) /* 0x10 */ +#define UB_ERS0_SA_H (0x44 + UB_CFG1_BASIC) /* 0x11 */ +#define UB_ERS1_SA_L (0x48 + UB_CFG1_BASIC) /* 0x12 */ +#define UB_ERS1_SA_H (0x4c + UB_CFG1_BASIC) /* 0x13 */ +#define UB_ERS2_SA_L (0x50 + UB_CFG1_BASIC) /* 0x14 */ +#define UB_ERS2_SA_H (0x54 + UB_CFG1_BASIC) /* 0x15 */ +#define UB_ERS0_UBBA_L (0x58 + UB_CFG1_BASIC) /* 0x16 */ +#define UB_ERS0_UBBA_H (0x5c + UB_CFG1_BASIC) /* 0x17 */ +#define UB_ERS1_UBBA_L (0x60 + UB_CFG1_BASIC) /* 0x18 */ +#define UB_ERS1_UBBA_H (0x64 + UB_CFG1_BASIC) /* 0x19 */ +#define UB_ERS2_UBBA_L (0x68 + UB_CFG1_BASIC) /* 0x1A */ +#define UB_ERS2_UBBA_H (0x6c + UB_CFG1_BASIC) /* 0x1B */ +#define UB_ELR (0x70 + UB_CFG1_BASIC) /* 0x1C */ +#define UB_ELR_BIT 0x1 +#define UB_ELR_DONE (0x74 + UB_CFG1_BASIC) /* 0x1D */ +#define UB_ELR_DONE_BIT 0x1 +#define UB_SYS_PGS (0x84 + UB_CFG1_BASIC) /* 0x21 */ +#define UB_SYS_PGS_SIZE 0x1 +#define UB_EU_TBA_L (0x88 + UB_CFG1_BASIC) /* 0x22 */ +#define UB_EU_TBA_H (0x8c + UB_CFG1_BASIC) /* 0x23 */ +#define UB_EU_TEN (0x90 + UB_CFG1_BASIC) /* 0x24 */ +#define UB_CLASS_CODE (0xa4 + UB_CFG1_BASIC) /* 0x29 */ +#define UB_BASE_CODE_MASK GENMASK(7, 0) +#define UB_SUB_CODE_MASK GENMASK(15, 8) +#define UB_ENTITY_TOKEN_ID (0xb4 + UB_CFG1_BASIC) /* 0x2D */ +#define UB_TOKEN_ID_MASK GENMASK(19, 0) +#define UB_BUS_ACCESS_EN (0xb8 + UB_CFG1_BASIC) /* 0x2E */ +#define UB_BUS_ACCESS_EN_BIT 0x1 +#define UB_ENTITY_RS_ACCESS_EN (0xbc + UB_CFG1_BASIC) /* 0x2F */ +#define UB_ENTITY_RS_ACCESS_EN_BIT 0x1 + +/* DECODER CAP */ +#define DECODER_CAP_START 0x00040400 +#define DECODER_POS(pos) (DECODER_CAP_START + ((pos) << 2)) +#define DECODER_CAP DECODER_POS(0x1) +#define DECODER_CTRL DECODER_POS(0x2) +#define DECODER_MATT_BA0 DECODER_POS(0x3) +#define DECODER_MATT_BA1 DECODER_POS(0x4) +#define DECODER_MMIO_BA0 DECODER_POS(0x5) +#define DECODER_MMIO_BA1 DECODER_POS(0x6) +#define DECODER_USI_IDX DECODER_POS(0x7) +#define DECODER_CMDQ_CFG DECODER_POS(0x10) +#define DECODER_CMDQ_PROD DECODER_POS(0x11) +#define DECODER_CMDQ_CONS DECODER_POS(0x12) +#define DECODER_CMDQ_BASE_ADDR0 DECODER_POS(0x13) +#define DECODER_CMDQ_BASE_ADDR1 DECODER_POS(0x14) +#define DECODER_EVENTQ_CFG DECODER_POS(0x20) +#define DECODER_EVENTQ_PROD DECODER_POS(0x21) +#define DECODER_EVENTQ_CONS DECODER_POS(0x22) +#define DECODER_EVENTQ_BASE_ADDR0 DECODER_POS(0x23) +#define DECODER_EVENTQ_BASE_ADDR1 DECODER_POS(0x24) + +/* INT_TYPE1_CAP */ +#define UB_CFG1_INT_TYPE1_CAP 0x40c00 +#define UB_INT_TYPE1_ENABLE (0x4 + UB_CFG1_INT_TYPE1_CAP) /* 0x1 */ +#define UB_INT_TYPE1_SUP_INT_NUM (0x8 + UB_CFG1_INT_TYPE1_CAP) /* 0x2 */ +#define UB_INT_TYPE1_EN_INT_NUM (0xC + UB_CFG1_INT_TYPE1_CAP) /* 0x3 */ +#define UB_INT_TYPE1_INT_DATA (0x10 + UB_CFG1_INT_TYPE1_CAP) /* 0x4 */ +#define UB_INT_TYPE1_INT_ADDR_L (0x14 + UB_CFG1_INT_TYPE1_CAP) /* 0x5 */ +#define UB_INT_TYPE1_INT_ADDR_H (0x18 + UB_CFG1_INT_TYPE1_CAP) /* 0x6 */ +#define UB_INT_TYPE1_INT_ID (0x1c + UB_CFG1_INT_TYPE1_CAP) /* 0x7 */ +#define UB_INT_TYPE1_INT_MASK (0x20 + UB_CFG1_INT_TYPE1_CAP) /* 0x8 */ +#define UB_INT_TYPE1_INT_PENDING (0x24 + UB_CFG1_INT_TYPE1_CAP) /* 0x9 */ + +/* INT_TYPE2_CAP */ +#define UB_CFG1_CAP_INT_CAP 0x41000 +#define UB_INT_CAP_SLICE (0x0 + UB_CFG1_CAP_INT_CAP) /* 0x0 */ +#define UB_INT_INFO (0x4 + UB_CFG1_CAP_INT_CAP) /* 0x1 */ +#define UB_NUM_OF_INTR_VECTOR_TBL (0x4 + UB_CFG1_CAP_INT_CAP) +#define UB_NUM_OF_INTR_ADDR_TBL (0x6 + UB_CFG1_CAP_INT_CAP) +#define UB_INT_VECTOR_TBL_SA_L (0x8 + UB_CFG1_CAP_INT_CAP) /* 0x2 */ +#define UB_INT_VECTOR_TBL_SA_H (0xc + UB_CFG1_CAP_INT_CAP) /* 0x3 */ +#define UB_INT_ADDR_TBL_SA_L (0x10 + UB_CFG1_CAP_INT_CAP) /* 0x4 */ +#define UB_INT_ADDR_TBL_SA_H (0x14 + UB_CFG1_CAP_INT_CAP) /* 0x5 */ +#define UB_INT_PENDING_TBL_SA_L (0x18 + UB_CFG1_CAP_INT_CAP) /* 0x6 */ +#define UB_INT_PENDING_TBL_SA_H (0x1c + UB_CFG1_CAP_INT_CAP) /* 0x7 */ +#define UB_INT_ID (0x20 + UB_CFG1_CAP_INT_CAP) /* 0x8 */ +#define UB_FUN_EN_BIT 0 +#define UB_FUN_MASK_BIT 1 +#define UB_INT_MASK (0x24 + UB_CFG1_CAP_INT_CAP) /* 0x9 */ +#define UB_INT_MASK_BIT 0x1 +#define UB_INT_EN (0x28 + UB_CFG1_CAP_INT_CAP) /* 0xA */ +#define UB_INT_EN_BIT 0x1 + +/* UB_MEM_CAP */ +#define UB_CFG1_CAP_UB_MEM 0x41800 +#define UB_MEM_USI_IDX (0x4 + UB_CFG1_CAP_UB_MEM) + +/* ub interrupt vector entry format */ +#define UB_INTR_VECTOR_ENTRY_SIZE 0x8 +#define UB_INTR_VECTOR_ID 0x0 +#define UB_INTR_VECTOR_ADDR_INDEX 0x4 +#define UB_INTR_VECTOR_ADDR_INDEX_MASK GENMASK(15, 0) +#define UB_INTR_VECTOR_MASK_MASK BIT(16) +/* ub interrupt addr entry format */ +#define UB_INTR_ADDR_ENTRY_SIZE 0x20 +#define UB_INTR_ADDR_ADDR_L 0x0 +#define UB_INTR_ADDR_ADDR_H 0x4 +#define UB_INTR_ADDR_TOKENID 0x8 +#define UB_INTR_ADDR_TOKENID_MASK GENMASK(19, 0) +#define UB_INTR_ADDR_VALID_BIT 20 +#define UB_INTR_ADDR_VALID_MASK BIT(20) +#define UB_INTR_ADDR_DSTEID_0 0xc +#define UB_INTR_ADDR_DSTEID_1 0x10 +#define UB_INTR_ADDR_DSTEID_2 0x14 +#define UB_INTR_ADDR_DSTEID_3 0x18 + +/* PORT BASIC */ +#define UB_CFG0_PORT_SLICE 0x0 +#define UB_CFG0_PORT_BITMAP 0x4 /* 0x1 */ +#define UB_PORT_INFO 0x24 /* 0x9 */ +#define UB_PORT_TYPE 0x26 /* 0x9 */ +#define UB_PORT_TYPE_MASK BIT(0) +#define UB_NEIGHBOR_PORT_INFO 0x28 /* 0xA */ +#define UB_UB_NEIGHBOR_PORT_INFO_SIZE 0x14 +#define UB_PORT_NET_ADDR 0x3c /* 0xF */ +#define UB_PORT_NET_ADDR_MASK GENMASK(23, 0) +#define UB_PORT_RST 0x40 /* 0x10 */ +#define UB_PORT_RST_BIT 0x1 + +/* PORT CAP1 LINK */ +/* Physical Port Link Status */ +#define UB_PORT_PHYSICAL_PORT_LINK_STATUS (0x1c0 << 2) +#define UB_PORT_LINK_STATE 0x1 + +/* PORT CAP15 QDLWS */ +#define PORT_CAP15_QDLWS_CAP (0xf01 << 2) +#define PORT_CAP15_QDLWS_CTRL (0xf02 << 2) +#define PORT_CAP15_QDLWS_STATE (0xf03 << 2) +#define ASY_LINK_WIDTH_MASK BIT(0) +#define GLB_QDLWS_MASK BIT(0) +#define GLB_QDLWS_ENABLE_MASK BIT(0) +#define GLB_QDLWS_DISABLE_MASK GENMASK(31, 1) +#define TX_QDLWS_MASK BIT(1) +#define TX_QDLWS_ENABLE_MASK BIT(1) +#define TX_QDLWS_DISABLE_MASK 0xFFFFFFFD +#define RX_QDLWS_MASK BIT(2) +#define RX_QDLWS_ENABLE_MASK BIT(2) +#define RX_QDLWS_DISABLE_MASK 0xFFFFFFFB +#define TX_WIDTH_MASK GENMASK(21, 16) +#define TX_WIDTH_OFFSET 16 +#define RX_WIDTH_MASK GENMASK(29, 24) +#define RX_WIDTH_OFFSET 24 +#define QDLWS_EXEC_STATUS_MASK GENMASK(2, 0) +#define QDLWS_EXEC_STATUS_MAX 4 + +#endif /* _UAPI_UB_UBUS_UBUS_REGS_H_ */ diff --git a/include/ub/ubase/ubase_comm_cmd.h b/include/ub/ubase/ubase_comm_cmd.h new file mode 100644 index 0000000000000000000000000000000000000000..311a309d10bd909857b24dd0cb8e7fed866fd70c --- /dev/null +++ b/include/ub/ubase/ubase_comm_cmd.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_CMD_H_ +#define _UB_UBASE_COMM_CMD_H_ + +#include +#include + +#define UBASE_FW_VERSION_BYTE3_MASK GENMASK(31, 24) +#define UBASE_FW_VERSION_BYTE2_MASK GENMASK(23, 16) +#define UBASE_FW_VERSION_BYTE1_MASK GENMASK(15, 8) +#define UBASE_FW_VERSION_BYTE0_MASK GENMASK(7, 0) + +#define UBASE_CSQ_BASEADDR_L_REG 0x18400 +#define UBASE_CSQ_BASEADDR_H_REG 0x18404 +#define UBASE_CSQ_DEPTH_REG 0x18408 +#define UBASE_CSQ_TAIL_REG 0x18410 +#define UBASE_CSQ_HEAD_REG 0x18414 +#define UBASE_CRQ_BASEADDR_L_REG 0x18418 +#define UBASE_CRQ_BASEADDR_H_REG 0x1841c +#define UBASE_CRQ_DEPTH_REG 0x18420 +#define UBASE_CRQ_TAIL_REG 0x18424 +#define UBASE_CRQ_HEAD_REG 0x18428 + +enum ubase_opcode_type { + /* Generic commands */ + UBASE_OPC_QUERY_FW_VER = 0x0001, + UBASE_OPC_QUERY_CTL_INFO = 0x0003, + UBASE_OPC_QUERY_COMM_RSRC_PARAM = 0x0030, + UBASE_OPC_QUERY_NIC_RSRC_PARAM = 0x0031, + UBASE_OPC_QUERY_LINK_STATUS = 0x0032, + UBASE_OPC_CFG_MTU = 0x0033, + UBASE_OPC_QUERY_NET_GUID = 0x0035, + UBASE_OPC_STATS_MAC_ALL = 0x0038, + UBASE_OPC_QUERY_BUS_EID = 0x0047, + UBASE_OPC_QUERY_UBCL_CONFIG = 0x0050, + + /* NL commands */ + UBASE_OPC_CFG_VL_MAP = 0x2206, + UBASE_OPC_CFG_ETS_TC_INFO = 0x2340, + UBASE_OPC_QUERY_ETS_TCG_INFO = 0x2341, + UBASE_OPC_QUERY_ETS_PORT_INFO = 0x2342, + UBASE_OPC_QUERY_VL_AGEING_EN = 0x2343, + UBASE_OPC_CFG_PROMISC_MODE = 0x240A, + + /* TP commands */ + UBASE_OPC_TP_TIMER_VA_CONFIG = 0x3007, + UBASE_OPC_TP_EXTDB_VA_CONFIG = 0x3008, + UBASE_OPC_TP_RSS_CONFIG = 0x300B, + UBASE_OPC_QUERY_CTP_VL_OFFSET = 0x3112, + + /* TA commands */ + UBASE_OPC_TA_EXTDB_VA_CONFIG = 0x4000, + UBASE_OPC_TA_TIMER_VA_CONFIG = 0x4001, + UBASE_OPC_QUERY_OOR_CAPS = 0x4200, + UBASE_OPC_QUERY_TA_SL_VL_MAP = 0x4201, + UBASE_OPC_TA_VL_SCH_CONFIG = 0x4202, + UBASE_OPC_VL_RATE_LIMIT_CONFIG = 0x4203, + UBASE_OPC_QUERY_TM_Q_INFO = 0x4205, + UBASE_OPC_QUERY_TM_QS_INFO = 0x4206, + UBASE_OPC_QUERY_TM_PRI_INFO = 0x4207, + UBASE_OPC_QUERY_TM_PG_INFO = 0x4208, + UBASE_OPC_QUERY_TM_PORT_INFO = 0x4209, + UBASE_OPC_QUERY_FST_FVT_RQMT = 0x4212, + + /* DL commands */ + UBASE_OPC_DL_CONFIG_MODE = 0x5100, + UBASE_OPC_QUERY_FLUSH_STATUS = 0x5102, + UBASE_OPC_START_PERF_STATS = 0x5103, + UBASE_OPC_STOP_PERF_STATS = 0x5104, + + /* PHY commands */ + UBASE_OPC_CONFIG_SPEED_DUP = 0x6100, + UBASE_OPC_CONFIG_AUTONEG_MODE = 0x6101, + UBASE_OPC_CONFIG_FEC_MODE = 0x6102, + UBASE_OPC_QUERY_PORT_INFO = 0x6200, + UBASE_OPC_QUERY_CHIP_INFO = 0x6201, + UBASE_OPC_QUERY_FEC_STATS = 0x6202, + + /* Mailbox commands */ + UBASE_OPC_POST_MB = 0x7000, + UBASE_OPC_QUERY_MB_ST = 0X7001, + + /* Ubctl commands */ + UBASE_OPC_QUERY_PORT_BITMAP = 0xA017, + + /* Software commands */ + UBASE_OPC_MUE_TO_UE = 0xF001, + UBASE_OPC_UE_TO_MUE = 0xF002, + UBASE_OPC_CFG_VPORT_BUF = 0xF003, + UBASE_OPC_NOTIFY_UE_RESET = 0xF006, + UBASE_OPC_QUERY_UE_RST_RDY = 0xF007, + UBASE_OPC_RESET_DONE = 0xF008, + UBASE_OPC_DESTROY_CTX_RESOURCE = 0xF00D, + UBASE_OPC_UE2UE_UBASE = 0xF00E, + UBASE_OPC_ACTIVATE_REQ = 0xF00F, + UBASE_OPC_ACTIVATE_RESP = 0xF010, +}; + +union ubase_mbox { + struct { + /* MB 0 */ + __le32 in_param_l; + /* MB 1 */ + __le32 in_param_h; + /* MB 2 */ + __le32 cmd : 8; + __le32 tag : 24; + /* MB 3 */ + __le32 seq_num : 16; + __le32 event_en : 1; + __le32 mbx_ue_id : 8; + __le32 rsv : 7; + /* MB 4 */ + __le32 status : 1; + __le32 hw_run : 1; + __le32 rsv1 : 30; + }; + + struct { + __le32 query_status : 1; + __le32 query_hw_run : 1; + __le32 query_rsv : 30; + }; +}; + +struct ubase_cmd_buf { + u16 opcode; + bool is_read; + u32 data_size; + void *data; +}; + +struct ubase_crq_event_nb { + u16 opcode; + void *back; + int (*crq_handler)(void *dev, void *data, u32 len); +}; + +static inline void ubase_fill_inout_buf(struct ubase_cmd_buf *buf, u16 opcode, + bool is_read, u32 data_size, void *data) +{ + buf->opcode = opcode; + buf->is_read = is_read; + buf->data_size = data_size; + buf->data = data; +} + +int ubase_cmd_send_inout(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in, struct ubase_cmd_buf *out); +int ubase_cmd_send_in(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in); +int ubase_cmd_send_inout_ex(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in, struct ubase_cmd_buf *out, + u32 time_out); +int ubase_cmd_send_in_ex(struct auxiliary_device *aux_dev, + struct ubase_cmd_buf *in, u32 time_out); + +int ubase_cmd_get_data_size(struct auxiliary_device *aux_dev, u16 opcode, + u16 *data_size); + +int ubase_register_crq_event(struct auxiliary_device *aux_dev, + struct ubase_crq_event_nb *nb); +void ubase_unregister_crq_event(struct auxiliary_device *aux_dev, u16 opcode); + +#endif diff --git a/include/ub/ubase/ubase_comm_ctrlq.h b/include/ub/ubase/ubase_comm_ctrlq.h new file mode 100644 index 0000000000000000000000000000000000000000..7e772dcb074650b27d6fde540313012b50e00678 --- /dev/null +++ b/include/ub/ubase/ubase_comm_ctrlq.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_CTRLQ_H_ +#define _UB_UBASE_COMM_CTRLQ_H_ + +#include +#include + +#define UBASE_CTRLQ_MAX_DATA_SIZE 1000U + +#define UBASE_CTRLQ_CSQ_BASEADDR_L_REG 0x18800 +#define UBASE_CTRLQ_CSQ_BASEADDR_H_REG 0x18804 +#define UBASE_CTRLQ_CSQ_DEPTH_REG 0x18808 +#define UBASE_CTRLQ_CSQ_TAIL_REG 0x18810 +#define UBASE_CTRLQ_CSQ_HEAD_REG 0x18814 +#define UBASE_CTRLQ_CRQ_BASEADDR_L_REG 0x18818 +#define UBASE_CTRLQ_CRQ_BASEADDR_H_REG 0x1881c +#define UBASE_CTRLQ_CRQ_DEPTH_REG 0x18820 +#define UBASE_CTRLQ_CRQ_TAIL_REG 0x18824 +#define UBASE_CTRLQ_CRQ_HEAD_REG 0x18828 + +enum ubase_ctrlq_ser_ver { + UBASE_CTRLQ_SER_VER_01 = 0x01, +}; + +enum ubase_ctrlq_ser_type { + UBASE_CTRLQ_SER_TYPE_TP_ACL = 0x01, + UBASE_CTRLQ_SER_TYPE_DEV_REGISTER = 0x02, + UBASE_CTRLQ_SER_TYPE_IP_ACL = 0x03, + UBASE_CTRLQ_SER_TYPE_QOS = 0x04, +}; + +enum ubase_ctrlq_opc_type { + UBASE_CTRLQ_OPC_CREATE_TP = 0x11, + UBASE_CTRLQ_OPC_DESTROY_TP = 0x12, + UBASE_CTRLQ_OPC_TP_FLUSH_DONE = 0x14, +}; + +enum ubase_ctrlq_opc_type_qos { + UBASE_CTRLQ_OPC_QUERY_VL = 0x01, + UBASE_CTRLQ_OPC_QUERY_SL = 0x02, +}; + +enum ubase_ctrlq_opc_ip { + UBASE_CTRLQ_OPC_NOTIFY_IP = 0x01, + UBASE_CTRLQ_OPC_QUERY_IP = 0x02, +}; + +enum ubase_ctrlq_opc_type_dev_register { + UBASE_CTRLQ_OPC_CTRLQ_CTRL = 0x14, + UBASE_CTRLQ_OPC_UE_RESET_CTRL = 0x15, +}; + +struct ubase_ctrlq_msg { + enum ubase_ctrlq_ser_ver service_ver; + enum ubase_ctrlq_ser_type service_type; + u8 opcode; + u8 need_resp : 1; + u8 is_resp : 1; + u8 resv : 6; + u8 resp_ret; /* must set when the is_resp field is true. */ + u16 resp_seq; /* must set when the is_resp field is true. */ + u16 in_size; + u16 out_size; + void *in; + void *out; +}; + +struct ubase_ctrlq_event_nb { + u8 service_type; + u8 opcode; + void *back; + int (*crq_handler)(struct auxiliary_device *adev, u8 service_ver, + void *data, u16 len, u16 seq); +}; + +int ubase_ctrlq_send_msg(struct auxiliary_device *aux_dev, + struct ubase_ctrlq_msg *msg); + +int ubase_ctrlq_register_crq_event(struct auxiliary_device *aux_dev, + struct ubase_ctrlq_event_nb *nb); +void ubase_ctrlq_unregister_crq_event(struct auxiliary_device *aux_dev, + u8 service_type, u8 opcode); + +#endif diff --git a/include/ub/ubase/ubase_comm_debugfs.h b/include/ub/ubase/ubase_comm_debugfs.h new file mode 100644 index 0000000000000000000000000000000000000000..dc0bd30aa93bf2aa9b8bdfaf30da1d185115dd6e --- /dev/null +++ b/include/ub/ubase/ubase_comm_debugfs.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_DEBUGFS_H_ +#define _UB_UBASE_COMM_DEBUGFS_H_ + +#include + +struct ubase_dbgfs; + +struct ubase_dbg_dentry_info { + const char *name; + struct dentry *dentry; + u32 property; + bool (*support)(struct device *dev, u32 property); +}; + +struct ubase_dbg_cmd_info { + const char *name; + int dentry_index; + u32 property; + bool (*support)(struct device *dev, u32 property); + int (*init)(struct device *dev, struct ubase_dbg_dentry_info *dirs, + struct ubase_dbgfs *dbgfs, u32 idx); + int (*read_func)(struct seq_file *s, void *data); +}; + +struct ubase_dbgfs { + struct dentry *dentry; /* dbgfs root path */ + struct ubase_dbg_cmd_info *cmd_info; + int cmd_info_size; +}; + +int ubase_dbg_seq_file_init(struct device *dev, + struct ubase_dbg_dentry_info *dirs, + struct ubase_dbgfs *dbgfs, u32 idx); +int ubase_dbg_create_dentry(struct device *dev, struct ubase_dbgfs *dbgfs, + struct ubase_dbg_dentry_info *dirs, u32 root_idx); +struct dentry *ubase_diag_debugfs_root(struct auxiliary_device *adev); +void ubase_print_context_hw(struct seq_file *s, void *ctx_addr, u32 ctx_len); +bool ubase_dbg_dentry_support(struct auxiliary_device *adev, u32 property); +int ubase_dbg_format_time(time64_t time, struct seq_file *s); + +#endif diff --git a/include/ub/ubase/ubase_comm_dev.h b/include/ub/ubase/ubase_comm_dev.h new file mode 100644 index 0000000000000000000000000000000000000000..81b2c98af17f4e3933bc8d6dd4995699433116bf --- /dev/null +++ b/include/ub/ubase/ubase_comm_dev.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_DEV_H_ +#define _UB_UBASE_COMM_DEV_H_ + +#include +#include +#include + +struct iova_slot; + +#define UBASE_ADEV_NAME "ubase" + +#define UBASE_IOVA_COMM_PFN_CNT 1 +#define UBASE_MAX_DSCP (64) +#define UBASE_MAX_SL_NUM (16U) +#define UBASE_MAX_REQ_VL_NUM (8U) +#define UBASE_MAX_VL_NUM (16U) +#if UBASE_MAX_VL_NUM < IEEE_8021QAZ_MAX_TCS +#error "UBASE_MAX_VL_NUM can't less than IEEE_8021QAZ_MAX_TCS" +#endif + +#define UBASE_SUP_UBL BIT(0) +#define UBASE_SUP_ETH BIT(1) +#define UBASE_SUP_UNIC BIT(2) +#define UBASE_SUP_UDMA BIT(3) +#define UBASE_SUP_CDMA BIT(4) +#define UBASE_SUP_PMU BIT(5) +#define UBASE_SUP_URMA (UBASE_SUP_UNIC | UBASE_SUP_UDMA) +#define UBASE_SUP_UBL_ETH (UBASE_SUP_UBL | UBASE_SUP_ETH) +#define UBASE_SUP_ALL (UBASE_SUP_UNIC | UBASE_SUP_UDMA | \ + UBASE_SUP_CDMA | UBASE_SUP_PMU) +#define UBASE_SUP_NO_PMU (UBASE_SUP_ALL ^ UBASE_SUP_PMU) + +enum ubase_reset_type { + UBASE_NO_RESET, + UBASE_ELR_RESET, + UBASE_UE_RESET, + UBASE_MAX_RESET, +}; + +enum ubase_reset_stage { + UBASE_RESET_STAGE_NONE, + UBASE_RESET_STAGE_DOWN, + UBASE_RESET_STAGE_UNINIT, + UBASE_RESET_STAGE_INIT, + UBASE_RESET_STAGE_UP, +}; + +struct ubase_caps { + u16 num_ceq_vectors; + u16 num_aeq_vectors; + u16 num_misc_vectors; + + u32 aeqe_depth; + u32 ceqe_depth; + u16 aeqe_size; + u16 ceqe_size; + + u32 total_ue_num; + u32 public_jetty_cnt; + u8 vl_num; + u16 rsvd_jetty_cnt; + + u8 req_vl[UBASE_MAX_VL_NUM]; + u8 resp_vl[UBASE_MAX_VL_NUM]; + + u8 packet_pattern_mode; + u8 ack_queue_num; + u8 oor_en; + u8 reorder_queue_en; + u32 on_flight_size; + u8 reorder_cap; + u8 reorder_queue_shift; + u8 at_times; + u8 ue_num; + u16 mac_stats_num; + + u32 logic_port_bitmap; + u16 ub_port_logic_id; + u16 io_port_logic_id; + u16 io_port_id; + u16 nl_port_id; + u16 chip_id; + u16 die_id; + u16 ue_id; + u16 nl_id; + + u32 tid; + u32 eid; + u16 upi; + u32 ctl_no; + + u32 fw_version; +}; + +struct ubase_res_caps { + u32 max_cnt; + u32 start_idx; + u32 reserved_cnt; + u32 depth; +}; + +struct ubase_pmem_caps { + u64 dma_len; + dma_addr_t dma_addr; +}; + +struct ubase_adev_caps { + struct ubase_res_caps jfs; + struct ubase_res_caps jfr; + struct ubase_res_caps jfc; + struct ubase_res_caps tp; + struct ubase_res_caps tpg; + struct ubase_pmem_caps pmem; + u32 utp_port_bitmap; /* utp port bitmap */ + u32 jtg_max_cnt; + u32 rc_max_cnt; + u32 rc_que_depth; + u32 ccc_max_cnt; + u32 dest_addr_max_cnt; + u32 seid_upi_max_cnt; + u32 tpm_max_cnt; + u16 cqe_size; +}; + +struct ubase_ctx_buf_cap { + dma_addr_t dma_ctx_buf_ba; /* pass to hw */ + struct iova_slot *slot; + u32 entry_size; + u32 entry_cnt; + u32 cnt_per_page_shift; + struct xarray ctx_xa; + struct mutex ctx_mutex; +}; + +struct ubase_ctx_buf { + struct ubase_ctx_buf_cap jfs; + struct ubase_ctx_buf_cap jfr; + struct ubase_ctx_buf_cap jfc; + struct ubase_ctx_buf_cap jtg; + struct ubase_ctx_buf_cap rc; + + struct ubase_ctx_buf_cap tp; + struct ubase_ctx_buf_cap tpg; +}; + +struct net_device; +struct ubase_adev_com { + struct auxiliary_device *adev; + struct net_device *netdev; +}; + +struct ubase_resource_space { + resource_size_t addr_unmapped; + void __iomem *addr; +}; + +struct ubase_adev_qos { + /* udma/cdma resource */ + u8 sl_num; + u8 sl[UBASE_MAX_SL_NUM]; + u8 tp_sl_num; + u8 tp_sl[UBASE_MAX_SL_NUM]; + u8 ctp_sl_num; + u8 ctp_sl[UBASE_MAX_SL_NUM]; + + u8 vl_num; + u8 vl[UBASE_MAX_VL_NUM]; + u8 tp_vl_num; + u8 tp_resp_vl_offset; + u8 tp_req_vl[UBASE_MAX_VL_NUM]; + u8 ctp_vl_num; + u8 ctp_resp_vl_offset; + u8 ctp_req_vl[UBASE_MAX_VL_NUM]; + + u8 dscp_vl[UBASE_MAX_DSCP]; + + /* unic resource */ + u8 nic_sl_num; + u8 nic_sl[UBASE_MAX_SL_NUM]; + + u8 nic_vl_num; + u8 nic_vl[UBASE_MAX_VL_NUM]; + + /* common resource */ + u8 ue_max_vl_id; + u8 ue_sl_vl[UBASE_MAX_SL_NUM]; +}; + +struct ubase_ue_node { + struct list_head list; + u16 bus_ue_id; +}; + +struct ubase_ue_caps { + u8 ceq_vector_num; + u8 aeq_vector_num; + u32 aeqe_depth; + u32 ceqe_depth; + u32 jfs_max_cnt; + u32 jfs_depth; + u32 jfr_max_cnt; + u32 jfr_depth; + u32 jfc_max_cnt; + u32 jfc_depth; + u32 rc_max_cnt; + u32 rc_depth; + u32 jtg_max_cnt; +}; + +#define UBASE_BUS_EID_LEN 4 +struct ubase_bus_eid { + u32 eid[UBASE_BUS_EID_LEN]; +}; + +bool ubase_adev_ubl_supported(struct auxiliary_device *adev); +bool ubase_adev_ctrlq_supported(struct auxiliary_device *adev); +bool ubase_adev_eth_mac_supported(struct auxiliary_device *adev); +bool ubase_adev_prealloc_supported(struct auxiliary_device *aux_dev); + +struct ubase_resource_space *ubase_get_io_base(struct auxiliary_device *adev); +struct ubase_resource_space *ubase_get_mem_base(struct auxiliary_device *adev); +struct ubase_caps *ubase_get_dev_caps(struct auxiliary_device *adev); +struct ubase_adev_caps *ubase_get_unic_caps(struct auxiliary_device *adev); +struct ubase_adev_caps *ubase_get_udma_caps(struct auxiliary_device *adev); +struct ubase_adev_caps *ubase_get_cdma_caps(struct auxiliary_device *adev); +struct ubase_adev_qos *ubase_get_adev_qos(struct auxiliary_device *adev); + +void ubase_reset_event(struct auxiliary_device *adev, + enum ubase_reset_type reset_type); +enum ubase_reset_stage ubase_get_reset_stage(struct auxiliary_device *adev); + +void ubase_virt_register(struct auxiliary_device *adev, + void (*virt_handler)(struct auxiliary_device *adev, + u16 bus_ue_id, bool is_en)); +void ubase_virt_unregister(struct auxiliary_device *adev); + +void ubase_port_register(struct auxiliary_device *adev, + void (*port_handler)(struct auxiliary_device *adev, + bool link_up)); +void ubase_port_unregister(struct auxiliary_device *adev); + +void ubase_reset_register(struct auxiliary_device *adev, + void (*reset_handler)(struct auxiliary_device *adev, + enum ubase_reset_stage stage)); +void ubase_reset_unregister(struct auxiliary_device *adev); + +void ubase_activate_register(struct auxiliary_device *adev, + void (*activate_handler)(struct auxiliary_device *adev, + bool activate)); +void ubase_activate_unregister(struct auxiliary_device *adev); + +int ubase_activate_dev(struct auxiliary_device *adev); +int ubase_deactivate_dev(struct auxiliary_device *adev); + +int ubase_get_bus_eid(struct auxiliary_device *adev, struct ubase_bus_eid *eid); + +#endif diff --git a/include/ub/ubase/ubase_comm_eq.h b/include/ub/ubase/ubase_comm_eq.h new file mode 100644 index 0000000000000000000000000000000000000000..fe97de1dd1ac3baf58bd9b77335500ea688751e5 --- /dev/null +++ b/include/ub/ubase/ubase_comm_eq.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_EQ_H_ +#define _UB_UBASE_COMM_EQ_H_ + +#include +#include + +enum ubase_drv_type { + UBASE_DRV_UNIC, + UBASE_DRV_UDMA, + UBASE_DRV_CDMA, + UBASE_DRV_FWCTL, + UBASE_DRV_PMU, + UBASE_DRV_UVB, + UBASE_DRV_MAX, +}; +enum ubase_subevent_jetty_type { + UBASE_SUBEVENT_TYPE_JFS_CHECK_ERROR = 0x01, + UBASE_SUBEVENT_TYPE_JFR_CHECK_ERROR, + UBASE_SUBEVENT_TYPE_JFC_CHECK_ERROR, + UBASE_SUBEVENT_TYPE_JETTY_GROUP_CHECK_ERROR +}; + +enum ubase_subevent_tp_type { + UBASE_SUBEVENT_TYPE_TP_RETRY_ERROR = 0x01, + UBASE_SUBEVENT_TYPE_TP_UNEXPECTED_OPCODE, + UBASE_SUBEVENT_TYPE_TP_PSN_MSN_MISMATCH, + UBASE_SUBEVENT_TYPE_TP_PORT_DOWN +}; + +enum ubase_subevent_ue_type { + UBASE_SUBEVENT_TYPE_ENTITY_LEVEL_ERROR = 0x00 +}; + +enum ubase_event_type { + UBASE_EVENT_TYPE_RESERVED = 0x00, + UBASE_EVENT_TYPE_JETTY_LEVEL_ERROR = 0x01, + UBASE_EVENT_TYPE_TP_LEVEL_ERROR = 0x02, + UBASE_EVENT_TYPE_ENTITY_LEVEL_ERROR = 0x03, + UBASE_EVENT_TYPE_TP_FLUSH_DONE = 0x10, + UBASE_EVENT_TYPE_ENTITY_FLUSH_DONE = 0x11, + UBASE_EVENT_TYPE_JFR_LIMIT_REACHED = 0x12, + UBASE_EVENT_TYPE_MB = 0x13, + UBASE_EVENT_TYPE_CHECK_TOKEN = 0x14, + UBASE_EVENT_TYPE_MAX +}; + +struct ubase_event_nb { + enum ubase_drv_type drv_type; + u8 event_type; + struct notifier_block nb; + void *back; +}; + +struct ubase_aeqe { + u32 event_type : 8; + u32 sub_type : 8; + u32 rsv0 : 15; + u32 owner : 1; + + union { + struct { + u32 num; + u32 rsv0; + u32 rsv1; + } queue_event; + +#pragma pack(push, 1) + struct { + u64 out_param; + u16 seq_num; + u8 status; + u8 rsv0; + } cmd; +#pragma pack(pop) + } event; + u32 rsv[12]; +}; + +struct ubase_aeq_notify_info { + u8 event_type; + u8 sub_type; + struct ubase_aeqe *aeqe; +}; + +int ubase_event_register(struct auxiliary_device *adev, + struct ubase_event_nb *cb); +void ubase_event_unregister(struct auxiliary_device *adev, + struct ubase_event_nb *cb); + +int ubase_comp_register(struct auxiliary_device *adev, + int (*comp_handler)(struct notifier_block *nb, + unsigned long jfcn, void *data)); +void ubase_comp_unregister(struct auxiliary_device *adev); + +#endif diff --git a/include/ub/ubase/ubase_comm_hw.h b/include/ub/ubase/ubase_comm_hw.h new file mode 100644 index 0000000000000000000000000000000000000000..ba3717fb16b377f9373e80be6df8d8550b4d5da8 --- /dev/null +++ b/include/ub/ubase/ubase_comm_hw.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_HW_H_ +#define _UB_UBASE_COMM_HW_H_ + +#include +#include + +#define UBASE_AEQ_CTX_SIZE 64 +#define UBASE_CEQ_CTX_SIZE 64 +#define UBASE_JFS_CTX_SIZE 256 +#define UBASE_JFR_CTX_SIZE 64 +#define UBASE_JFC_CTX_SIZE 128 +#define UBASE_RC_CTX_SIZE 256 +#define UBASE_JTG_CTX_SIZE 8 + +#define UBASE_DESC_DATA_LEN 6 +struct ubase_cmdq_desc { + __le16 opcode; + u8 flag; + u8 bd_num; + __le16 ret; + __le16 rsv; + __le32 data[UBASE_DESC_DATA_LEN]; +}; + +struct ubase_cmdq_ring { + u32 ci; + u32 pi; + u32 desc_num; + u32 tx_timeout; + dma_addr_t desc_dma_addr; + struct ubase_cmdq_desc *desc; + spinlock_t lock; +}; + +struct ubase_cmdq { + struct ubase_cmdq_ring csq; + struct ubase_cmdq_ring crq; +}; + +struct ubase_hw { + struct ubase_resource_space rs0_base; + struct ubase_resource_space io_base; + struct ubase_resource_space mem_base; + struct ubase_cmdq cmdq; + unsigned long state; +}; + +struct ubase_mbx_event_context { + struct completion done; + int result; + u64 out_param; + u16 seq_num; +}; + +#endif diff --git a/include/ub/ubase/ubase_comm_mbx.h b/include/ub/ubase/ubase_comm_mbx.h new file mode 100644 index 0000000000000000000000000000000000000000..26c625f80a778906eeedaa0e6c1f9b2f10b35ef3 --- /dev/null +++ b/include/ub/ubase/ubase_comm_mbx.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_MBX_H_ +#define _UB_UBASE_COMM_MBX_H_ + +#include +#include + +struct ubase_cmd_mailbox { + void *buf; + dma_addr_t dma; +}; + +struct ubase_mbx_attr { + __le32 tag : 24; + __le32 rsv : 8; + u8 op; + u8 mbx_ue_id; +}; + +enum ubase_mbox_opcode { + /* write/destroy jfs/jetty ctx buf */ + UBASE_MB_WRITE_JFS_CONTEXT_VA = 0x0, + UBASE_MB_CREATE_JFS_CONTEXT = 0x4, + UBASE_MB_MODIFY_JFS_CONTEXT = 0x5, + UBASE_MB_QUERY_JFS_CONTEXT = 0x6, + UBASE_MB_DESTROY_JFS_CONTEXT = 0x7, + + /* write/destroy rc ctx buf */ + UBASE_MB_WRITE_RC_CONTEXT_VA = 0x10, + UBASE_MB_CREATE_RC_CONTEXT = 0x14, + UBASE_MB_MODIFY_RC_CONTEXT = 0x15, + UBASE_MB_QUERY_RC_CONTEXT = 0x16, + UBASE_MB_DESTROY_RC_CONTEXT = 0x17, + + /* write/destroy jfc ctx buf */ + UBASE_MB_WRITE_JFC_CONTEXT_VA = 0x20, + UBASE_MB_CREATE_JFC_CONTEXT = 0x24, + UBASE_MB_MODIFY_JFC_CONTEXT = 0x25, + UBASE_MB_QUERY_JFC_CONTEXT = 0x26, + UBASE_MB_DESTROY_JFC_CONTEXT = 0x27, + + /* create/destroy aeq ctx */ + UBASE_MB_CREATE_AEQ_CONTEXT = 0x34, + UBASE_MB_QUERY_AEQ_CONTEXT = 0x36, + UBASE_MB_DESTROY_AEQ_CONTEXT = 0x37, + + /* create/destroy ceq ctx */ + UBASE_MB_CREATE_CEQ_CONTEXT = 0x44, + UBASE_MB_QUERY_CEQ_CONTEXT = 0x46, + UBASE_MB_DESTROY_CEQ_CONTEXT = 0x47, + + /* write/destroy jfr ctx buf */ + UBASE_MB_WRITE_JFR_CONTEXT_VA = 0x50, + UBASE_MB_CREATE_JFR_CONTEXT = 0x54, + UBASE_MB_MODIFY_JFR_CONTEXT = 0x55, + UBASE_MB_QUERY_JFR_CONTEXT = 0x56, + UBASE_MB_DESTROY_JFR_CONTEXT = 0x57, + + /* write/destroy jetty group ctx buf */ + UBASE_MB_WRITE_JETTY_GROUP_CONTEXT_VA = 0x60, + UBASE_MB_CREATE_JETTY_GROUP_CONTEXT = 0x64, + UBASE_MB_MODIFY_JETTY_GROUP_CONTEXT = 0x65, + UBASE_MB_QUERY_JETTY_GROUP_CONTEXT = 0x66, + UBASE_MB_DESTROY_JETTY_GROUP_CONTEXT = 0x67, + + /* query tpg ctx buf */ + UBASE_MB_QUERY_TPG_CONTEXT = 0x76, + + /* query tp ctx buf */ + UBASE_MB_QUERY_TP_CONTEXT = 0x86, +}; + +struct ubase_cmd_mailbox *ubase_alloc_cmd_mailbox(struct auxiliary_device *aux_dev); +void ubase_free_cmd_mailbox(struct auxiliary_device *aux_dev, + struct ubase_cmd_mailbox *mailbox); + +int ubase_hw_upgrade_ctx_ex(struct auxiliary_device *aux_dev, + struct ubase_mbx_attr *attr, + struct ubase_cmd_mailbox *mailbox); + +static inline void ubase_fill_mbx_attr(struct ubase_mbx_attr *attr, u32 tag, + u8 op, u8 mbx_ue_id) +{ + attr->tag = tag; + attr->op = op; + attr->mbx_ue_id = mbx_ue_id; +} + +#endif diff --git a/include/ub/ubase/ubase_comm_qos.h b/include/ub/ubase/ubase_comm_qos.h new file mode 100644 index 0000000000000000000000000000000000000000..01fc6942ca01d9d49c62b7ddde99eb3b56a75c56 --- /dev/null +++ b/include/ub/ubase/ubase_comm_qos.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_QOS_H_ +#define _UB_UBASE_COMM_QOS_H_ + +#include +#include +#include + +enum ubase_sl_sched_mode { + UBASE_SL_SP = IEEE_8021QAZ_TSA_STRICT, + UBASE_SL_DWRR = IEEE_8021QAZ_TSA_ETS, +}; + +struct ubase_sl_priqos { + u32 port_bitmap; + u32 sl_bitmap; + u8 weight[UBASE_MAX_SL_NUM]; + u8 sch_mode[UBASE_MAX_SL_NUM]; +}; + +int ubase_set_priqos_info(struct device *dev, struct ubase_sl_priqos *sl_priqos); +int ubase_get_priqos_info(struct device *dev, struct ubase_sl_priqos *sl_priqos); +int ubase_check_qos_sch_param(struct auxiliary_device *adev, u16 vl_bitmap, + u8 *vl_bw, u8 *vl_tsa, bool is_ets); +int ubase_config_tm_vl_sch(struct auxiliary_device *adev, u16 vl_bitmap, + u8 *vl_bw, u8 *vl_tsa); +void ubase_update_udma_dscp_vl(struct auxiliary_device *adev, u8 *dscp_vl, + u8 dscp_num); + +#endif diff --git a/include/ub/ubase/ubase_comm_stats.h b/include/ub/ubase/ubase_comm_stats.h new file mode 100644 index 0000000000000000000000000000000000000000..fdafc18e6700ebb29104b264ef08cb6fb6c6c5f1 --- /dev/null +++ b/include/ub/ubase/ubase_comm_stats.h @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved. + * + */ + +#ifndef _UB_UBASE_COMM_STATS_H_ +#define _UB_UBASE_COMM_STATS_H_ + +#include + +#define UBASE_STATS_MAX_VL_NUM 16 +#define UBASE_MAX_PORT_NUM 64 + +struct ubase_ub_dl_stats { + u64 dl_tx_vl0_pkt_num; + u64 dl_tx_vl1_pkt_num; + u64 dl_tx_vl2_pkt_num; + u64 dl_tx_vl3_pkt_num; + u64 dl_tx_vl4_pkt_num; + u64 dl_tx_vl5_pkt_num; + u64 dl_tx_vl6_pkt_num; + u64 dl_tx_vl7_pkt_num; + u64 dl_tx_vl8_pkt_num; + u64 dl_tx_vl9_pkt_num; + u64 dl_tx_vl10_pkt_num; + u64 dl_tx_vl11_pkt_num; + + u64 dl_rx_vl0_pkt_num; + u64 dl_rx_vl1_pkt_num; + u64 dl_rx_vl2_pkt_num; + u64 dl_rx_vl3_pkt_num; + u64 dl_rx_vl4_pkt_num; + u64 dl_rx_vl5_pkt_num; + u64 dl_rx_vl6_pkt_num; + u64 dl_rx_vl7_pkt_num; + u64 dl_rx_vl8_pkt_num; + u64 dl_rx_vl9_pkt_num; + u64 dl_rx_vl10_pkt_num; + u64 dl_rx_vl11_pkt_num; + + u64 dl_tx_busi_pkt_num; + u64 dl_tx_busi_block_num; + u64 dl_tx_busi_flit_num; + u64 dl_rx_busi_pkt_num; + u64 dl_rx_busi_block_num; + u64 dl_rx_busi_flit_num; + u64 dl_tx_ctrl_pkt_num; + u64 dl_tx_ctrl_pkt_flit_num; + u64 dl_rx_ctrl_pkt_num; + u64 dl_rx_ctrl_pkt_flit_num; + + u64 dl_rx_vl0_from_nl_crd; + u64 dl_rx_vl1_from_nl_crd; + u64 dl_rx_vl2_from_nl_crd; + u64 dl_rx_vl3_from_nl_crd; + u64 dl_rx_vl4_from_nl_crd; + u64 dl_rx_vl5_from_nl_crd; + u64 dl_rx_vl6_from_nl_crd; + u64 dl_rx_vl7_from_nl_crd; + u64 dl_rx_vl8_from_nl_crd; + u64 dl_rx_vl9_from_nl_crd; + u64 dl_rx_vl10_from_nl_crd; + u64 dl_rx_vl11_from_nl_crd; + + u64 dl_tx_vl0_to_nl_crd; + u64 dl_tx_vl1_to_nl_crd; + u64 dl_tx_vl2_to_nl_crd; + u64 dl_tx_vl3_to_nl_crd; + u64 dl_tx_vl4_to_nl_crd; + u64 dl_tx_vl5_to_nl_crd; + u64 dl_tx_vl6_to_nl_crd; + u64 dl_tx_vl7_to_nl_crd; + u64 dl_tx_vl8_to_nl_crd; + u64 dl_tx_vl9_to_nl_crd; + u64 dl_tx_vl10_to_nl_crd; + u64 dl_tx_vl11_to_nl_crd; + + u64 dl_tx_recv_ack_flit; + u64 dl_rx_send_ack_flit; + u64 dl_retry_req_sum; + u64 dl_retry_ack_sum; + u64 dl_crc_error_sum; + + u64 dl_tx_vl12_pkt_num; + u64 dl_tx_vl13_pkt_num; + u64 dl_tx_vl14_pkt_num; + u64 dl_tx_vl15_pkt_num; + + u64 dl_rx_vl12_pkt_num; + u64 dl_rx_vl13_pkt_num; + u64 dl_rx_vl14_pkt_num; + u64 dl_rx_vl15_pkt_num; + + u64 dl_rx_vl12_from_nl_crd; + u64 dl_rx_vl13_from_nl_crd; + u64 dl_rx_vl14_from_nl_crd; + u64 dl_rx_vl15_from_nl_crd; + + u64 dl_tx_vl12_to_nl_crd; + u64 dl_tx_vl13_to_nl_crd; + u64 dl_tx_vl14_to_nl_crd; + u64 dl_tx_vl15_to_nl_crd; +}; + +struct ubase_eth_mac_stats { + u64 tx_fragment_pkts; + u64 tx_undersize_pkts; + u64 tx_undermin_pkts; + + u64 tx_64_octets_pkts; + u64 tx_65_127_octets_pkts; + u64 tx_128_255_octets_pkts; + u64 tx_256_511_octets_pkts; + u64 tx_512_1023_octets_pkts; + u64 tx_1024_1518_octets_pkts; + u64 tx_1519_2047_octets_pkts; + u64 tx_2048_4095_octets_pkts; + u64 tx_4096_8191_octets_pkts; + u64 tx_8192_9216_octets_pkts; + u64 tx_9217_12287_octets_pkts; + u64 tx_12288_16383_octets_pkts; + u64 tx_1519_max_octets_bad_pkts; + u64 tx_1519_max_octets_good_pkts; + u64 tx_oversize_pkts; + u64 tx_jabber_pkts; + u64 tx_bad_pkts; + u64 tx_bad_octets; + u64 tx_good_pkts; + u64 tx_good_octets; + u64 tx_total_pkts; + u64 tx_total_octets; + u64 tx_unicast_pkts; + u64 tx_multicast_pkts; + u64 tx_broadcast_pkts; + + u64 tx_pause_pkts; + u64 tx_pfc_pkts; + u64 tx_pri0_pfc_pkts; + u64 tx_pri1_pfc_pkts; + u64 tx_pri2_pfc_pkts; + u64 tx_pri3_pfc_pkts; + u64 tx_pri4_pfc_pkts; + u64 tx_pri5_pfc_pkts; + u64 tx_pri6_pfc_pkts; + u64 tx_pri7_pfc_pkts; + + u64 tx_mac_ctrl_pkts; + u64 tx_unfilter_pkts; + u64 tx_1588_pkts; + u64 tx_err_all_pkts; + u64 tx_from_app_good_pkts; + u64 tx_from_app_bad_pkts; + + u64 rx_fragment_pkts; + u64 rx_undersize_pkts; + u64 rx_undermin_pkts; + + u64 rx_64_octets_pkts; + u64 rx_65_127_octets_pkts; + u64 rx_128_255_octets_pkts; + u64 rx_256_511_octets_pkts; + u64 rx_512_1023_octets_pkts; + u64 rx_1024_1518_octets_pkts; + u64 rx_1519_2047_octets_pkts; + u64 rx_2048_4095_octets_pkts; + u64 rx_4096_8191_octets_pkts; + u64 rx_8192_9216_octets_pkts; + u64 rx_9217_12287_octets_pkts; + u64 rx_12288_16383_octets_pkts; + u64 rx_1519_max_octets_bad_pkts; + u64 rx_1519_max_octets_good_pkts; + + u64 rx_oversize_pkts; + u64 rx_jabber_pkts; + u64 rx_bad_pkts; + u64 rx_bad_octets; + u64 rx_good_pkts; + u64 rx_good_octets; + u64 rx_total_pkts; + u64 rx_total_octets; + u64 rx_unicast_pkts; + u64 rx_multicast_pkts; + u64 rx_broadcast_pkts; + + u64 rx_pause_pkts; + u64 rx_pfc_pkts; + u64 rx_pri0_pfc_pkts; + u64 rx_pri1_pfc_pkts; + u64 rx_pri2_pfc_pkts; + u64 rx_pri3_pfc_pkts; + u64 rx_pri4_pfc_pkts; + u64 rx_pri5_pfc_pkts; + u64 rx_pri6_pfc_pkts; + u64 rx_pri7_pfc_pkts; + + u64 rx_mac_ctrl_pkts; + u64 rx_symbol_err_pkts; + u64 rx_fcs_err_pkts; + u64 rx_send_app_good_pkts; + u64 rx_send_app_bad_pkts; + u64 rx_unfilter_pkts; + + u64 tx_merge_frame_ass_error_pkts; + u64 tx_merge_frame_ass_ok_pkts; + u64 tx_merge_frame_frag_count; + u64 rx_merge_frame_ass_error_pkts; + u64 rx_merge_frame_ass_ok_pkts; + u64 rx_merge_frame_frag_count; + u64 rx_merge_frame_smd_error_pkts; +}; + +struct ubase_perf_stats_result { + u8 valid : 1; + u8 resv0 : 7; + u8 port_id; + u8 resv1[2]; + u32 tx_port_bw; /* kbps */ + u32 rx_port_bw; + u32 tx_vl_bw[UBASE_STATS_MAX_VL_NUM]; + u32 rx_vl_bw[UBASE_STATS_MAX_VL_NUM]; +}; + +int ubase_get_ub_port_stats(struct auxiliary_device *adev, u16 port_id, + struct ubase_ub_dl_stats *data); +int ubase_perf_stats(struct auxiliary_device *adev, u64 port_bitmap, u32 period, + struct ubase_perf_stats_result *data, u32 data_size); + +#endif /* _UBASE_COMM_STATS_H */ diff --git a/include/ub/ubfi/ubfi.h b/include/ub/ubfi/ubfi.h new file mode 100644 index 0000000000000000000000000000000000000000..5867311b10a63edded76a3ae34a2c201ff69f5aa --- /dev/null +++ b/include/ub/ubfi/ubfi.h @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef _UB_UBFI_UBFI_H_ +#define _UB_UBFI_UBFI_H_ + +#include +#include +#include +#include +#include + +#define UMMU_RSV_LEN 26 +#define UMMU_VEND_LEN 10 + +enum ubrt_node_type { + UBRT_UBC, + UBRT_UMMU, + UBRT_UMMU_PMU, +}; + +/* + * struct ubrt_fwnode - Structure to store nodes of the UBRT table reported by + * the BIOS. The UBRT nodes describe hardware information + * for UB system devices, such as UBC devices, UMMU + * devices, etc. + * + * @type: Type of the UBRT node. + * @index: Index of the UBRT node within its type. + * @fwnode: fwnode_handle of the device corresponding to the UBRT node. + * @ubrt_node: Pointer to the UBRT node, e.g., an ummu_node. + * @list: List node of ubrt_fwnode_list. + */ +struct ubrt_fwnode { + enum ubrt_node_type type; + u32 index; + struct fwnode_handle *fwnode; + void *ubrt_node; + struct list_head list; +}; + +/* + * struct ummu_node - Structure to store hardware information nodes reported + * by the BIOS to the UMMU device. + * + * @base_addr: Base address of the UMMU register space. + * @addr_size: Size of the UMMU register space. + * @intr_id: Interrupt ID for the UMMU device, used for device interrupt + * request in the OS. + * @pxm: Proximity Domain (PXM) identifier of the UMMU device. If the value is + * 0xFFFF, it indicates an invalid PXM. + * @its_index: Index of the Interrupt Translation Service (ITS) associated with + * the UMMU device. + * @pmu_addr: Base address of the PMU register space associated with the UMMU + * device. + * @pmu_size: Size of the PMU register space associated with the UMMU device. + * @pmu_intr_id: Interrupt ID for the PMU associated with the UMMU device. + * @min_tid: Minimum Token ID that can be allocated. + * @max_tid: Maximum Token ID that can be allocated. + * @reserved: Reserved bytes. + * @vendor_id: Vendor ID, reused from the Vendor ID definition in GUID, see + * "UB Base Specification 2.0". + * @vendor_info: Vendor-specific information. + */ +struct ummu_node { + u64 base_addr; + u64 addr_size; + u32 intr_id; + u16 pxm; + u16 its_index; + u64 pmu_addr; + u64 pmu_size; + u32 pmu_intr_id; + u32 min_tid; + u32 max_tid; + u8 reserved[UMMU_RSV_LEN]; + u16 vendor_id; + u64 vendor_info[UMMU_VEND_LEN]; +}; + +/** + * ubrt_register_gsi() - Registering UBC's interrupt into the kernel via ACPI + * @hwirq: GSI IRQ number reported by BIOS + * @trigger: trigger type of the GSI number to be mapped + * @polarity: polarity of the GSI to be mapped + * @name: GSI IRQ name + * @res: Record the soft interrupt number obtained after registration into res + * + * Return: 0 if success or other if failed + */ +int ubrt_register_gsi(u32 hwirq, int trigger, int polarity, const char *name, + struct resource *res); + +/** + * ubrt_unregister_gsi() - Unregistering UBC's interrupt into the kernel via ACPI + * @hwirq: GSI IRQ number reported by BIOS + */ +void ubrt_unregister_gsi(u32 hwirq); + +/** + * ub_update_msi_domain() - Update the MSI domain of UBC + * @dev: device with ub msi domain + * @bus_token: DOMAIN_BUS_UB_MSI + * + * Used when booting via ACPI. The MSI domain of the UB is reported by a + * platform device to the driver, and this function passes the MSI domain of the + * platform device to the UBC. + * + * Return: 0 if success or other if failed + */ +int ub_update_msi_domain(struct device *dev, + enum irq_domain_bus_token bus_token); + +/** + * ubrt_fwnode_set() - Associate a device's fwnode with an UBRT node + * @index: Index of the UBRT node within its type. + * @type: Type of the UBRT node. + * @fwnode: fwnode_handle associated with a device. + * + * Finds an UBRT node by index and type, and associates the device's + * fwnode with the UBRT node. This allows subsequent lookups of + * the UBRT node using the device's fwnode. + * + * Return: 0 if success, or error code if failed. + */ +int ubrt_fwnode_set(u32 index, enum ubrt_node_type type, + struct fwnode_handle *fwnode); + +/** + * ubrt_fwnode_get_by_idx() - Get an UBRT node by index and type + * @index: Index of the UBRT node within its type. + * @type: Type of the UBRT node. + * + * Finds an UBRT node by index and type. + * + * Return: Pointer to the struct ubrt_fwnode if found, or NULL if not found. + */ +struct ubrt_fwnode *ubrt_fwnode_get_by_idx(u32 index, + enum ubrt_node_type type); + +/** + * ubrt_fwnode_get() - Get an UBRT node by fwnode_handle + * @fwnode: fwnode_handle associated with a device. + * + * Finds an UBRT node by the device's fwnode. + * + * Return: Pointer to struct ubrt_fwnode if found, or NULL if not found. + */ +struct ubrt_fwnode *ubrt_fwnode_get(struct fwnode_handle *fwnode); + +/** + * ubrt_fwnode_get_count() - Get the count of UBRT nodes of a specific type + * @type: Type of the UBRT node. + * + * Return: The count of UBRT nodes of the specified type. + */ +u32 ubrt_fwnode_get_count(enum ubrt_node_type type); + +/** + * ubrt_fwnode_add() - Add an UBRT node to the ubrt_fwnode_list + * @node: Pointer to the UBRT node. + * @index: Index of the UBRT node within its type. + * @size: Size of the UBRT node. + * @type: Type of the UBRT node. + * + * Adds an UBRT node to the ubrt_fwnode_list, associating it with the + * specified index and type. + * + * Return: 0 if success, or error code if failed. + */ +int ubrt_fwnode_add(void *node, u32 index, int size, enum ubrt_node_type type); + +/** + * ubrt_fwnode_del() - Delete an UBRT node from the ubrt_fwnode_list + * @index: Index of the UBRT node within its type. + * @type: Type of the UBRT node. + */ +void ubrt_fwnode_del(u32 index, enum ubrt_node_type type); + +/** + * ubrt_fwnode_del_all() - Clear the ubrt_fwnode_list + * + * Clears all UBRT nodes from the ubrt_fwnode_list. + */ +void ubrt_fwnode_del_all(void); + +/** + * ubrt_get_interrupt_id() - Get the interrupt ID of an UMMU node + * @ummu_map: Index of the UMMU device. + * @intr_id: Pointer to the interrupt ID. + * + * Obtain the intr_id of ummu_node through ummu_map. + * + * Return: 0 if success, or error code if failed. + */ +int ubrt_get_interrupt_id(u16 ummu_map, u32 *intr_id); + +#if IS_ENABLED(CONFIG_UB_UBFI) +extern struct list_head ubc_list; +extern u32 ubc_eid_start; +extern u32 ubc_eid_end; +extern u32 ubc_cna_start; +extern u32 ubc_cna_end; +extern u8 ubc_feature; + +/** + * ubrt_iommu_get_resv_regions() - Get reserved memory regions from UBRT table. + * @dev: Pointer to the device. + * @list: List used to store reserved memory regions. + * + * In virtualization scenarios, the host OS should describe the available + * memory regions for UBC to the guest OS through the UB Reserved Memory + * information table in the UBRT table. This information table is reported by + * the VM BIOS; the physical machine BIOS does not need to report this + * information table. + */ +void ubrt_iommu_get_resv_regions(struct device *dev, struct list_head *list); +#endif /* CONFIG_UB_UBFI */ + +#if IS_ENABLED(CONFIG_UB_UBRT_PLAT_DEV) +int ubrt_pmsi_get_interrupt_id(struct device *dev, u32 *interrupt_id); +#else +static inline int ubrt_pmsi_get_interrupt_id(struct device *dev, u32 *interrupt_id) +{ return -ENODEV; } +#endif /* CONFIG_UB_UBRT_PLAT_DEV */ + +#endif /* _UB_UBFI_UBFI_H_ */ diff --git a/include/ub/ubus/ub-mem-decoder.h b/include/ub/ubus/ub-mem-decoder.h new file mode 100644 index 0000000000000000000000000000000000000000..322fa7d7b01e12d655b176496ec5dbc913c652be --- /dev/null +++ b/include/ub/ubus/ub-mem-decoder.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef _UB_UBUS_UB_MEM_DECODER_H_ +#define _UB_UBUS_UB_MEM_DECODER_H_ + +#include + +enum ras_err_type { + UB_MEM_ATOMIC_DATA_ERR = 0, + UB_MEM_READ_DATA_ERR, + UB_MEM_FLOW_POISON, + UB_MEM_FLOW_READ_AUTH_POISON, + UB_MEM_FLOW_READ_AUTH_RESPERR, + UB_MEM_TIMEOUT_POISON, + UB_MEM_TIMEOUT_RESPERR, + UB_MEM_READ_DATA_POISON, + UB_MEM_READ_DATA_RESPERR, + MAR_NOPORT_VLD_INT_ERR, + MAR_FLUX_INT_ERR, + MAR_WITHOUT_CXT_ERR, + RSP_BKPRE_OVER_TIMEOUT_ERR, + MAR_NEAR_AUTH_FAIL_ERR, + MAR_FAR_AUTH_FAIL_ERR, + MAR_TIMEOUT_ERR, + MAR_ILLEGAL_ACCESS_ERR, + REMOTE_READ_DATA_ERR_OR_WRITE_RESPONSE_ERR, +}; + +typedef int (*ubmem_ras_handler)(u64, enum ras_err_type); + +#ifdef CONFIG_UB_UBUS + +/* + * ub_mem_ras_handler_register - register ub memory ras handler for OBMM + * @handler: OBMM ras handler + */ +void ub_mem_ras_handler_register(ubmem_ras_handler handler); + +/* + * ub_mem_ras_handler_unregister - unregister ub memory ras handler for OBMM + */ +void ub_mem_ras_handler_unregister(void); + +/* + * ub_mem_ras_handler_get - get ub memory ras handler + * RETURN VALUE: ubmem_ras_handler + */ +ubmem_ras_handler ub_mem_ras_handler_get(void); + +/* + * ub_mem_drain_start - start ub memory drain + * @scna: source cna + */ +void ub_mem_drain_start(u32 scna); + +/* + * ub_mem_drain_state - whether ub memory drain has been finished + * @scna: source cna + * RETURN VALUE: + * 0 if drain not finish; 1 if drain finish + * other if failed. + */ +int ub_mem_drain_state(u32 scna); + +/* + * ub_mem_drain_start_enhanced - start ub memory drain enhanced + */ +void ub_mem_drain_start_enhanced(void); + +/* + * ub_mem_drain_state_enhanced - whether ub memory drain enhanced has been finished + * RETURN VALUE: + * 0 if drain not finish; 1 if drain finish + * other if failed. + */ +int ub_mem_drain_state_enhanced(void); + +/* + * ub_mem_get_numa_id - get ubc numa id from scna + * @scna: source cna + * RETURN VALUE: + * numa id + */ +int ub_mem_get_numa_id(u32 scna); + +/* + * ub_memory_validate_pa - Determine whether hpa is valid + * @scna: source cna + * @pa_start: hpa start address + * @pa_end: hpa end address + * @cacheable: cacheable flag + * RETURN VALUE: + * true if hpa is valid + * false if hpa is invalid + */ +bool ub_memory_validate_pa(u32 scna, u64 pa_start, u64 pa_end, bool cacheable); + +#else /* CONFIG_UB_UBUS is not enabled */ +static inline void ub_mem_ras_handler_register(ubmem_ras_handler handler) {} +static inline void ub_mem_ras_handler_unregister(void) {} +static inline ubmem_ras_handler ub_mem_ras_handler_get(void) { return NULL; } +static inline void ub_mem_drain_start(u32 scna) {} +static inline int ub_mem_drain_state(u32 scna) { return -EINVAL; } +static inline int ub_mem_get_numa_id(u32 scna) { return NUMA_NO_NODE; } +static inline bool ub_memory_validate_pa(u32 scna, u64 pa_start, u64 pa_end, + bool cacheable) +{ return false; } +#endif /* CONFIG_UB_UBUS */ + +#endif /* _UB_UBUS_UB_MEM_DECODER_H_ */ diff --git a/include/ub/ubus/uber.h b/include/ub/ubus/uber.h new file mode 100644 index 0000000000000000000000000000000000000000..73a4418d5894a34fdff0b040d16027519b023687 --- /dev/null +++ b/include/ub/ubus/uber.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef _UB_UBUS_UBER_H_ +#define _UB_UBUS_UBER_H_ + +#include + +typedef void (*ub_ras_recover_func_t)(struct cper_sec_ubus *err, int serverity); + +#ifdef CONFIG_UB_UBUS +void ub_ras_register_recover_func(ub_ras_recover_func_t func); +ub_ras_recover_func_t ub_ras_get_recover_func(void); +#else +static inline void ub_ras_register_recover_func(ub_ras_recover_func_t func) {} +static inline ub_ras_recover_func_t ub_ras_get_recover_func(void) +{ + return NULL; +} +#endif /* CONFIG_UB_UBUS */ + +#endif /* _UB_UBUS_UBER_H_ */ diff --git a/include/ub/ubus/ubus.h b/include/ub/ubus/ubus.h new file mode 100644 index 0000000000000000000000000000000000000000..874eace9271c94445f0a7e3311eedfd38650b797 --- /dev/null +++ b/include/ub/ubus/ubus.h @@ -0,0 +1,1111 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ + +#ifndef _UB_UBUS_UBUS_H_ +#define _UB_UBUS_UBUS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UB_ENTITY(v, d) \ + .vendor = (v), .device = (d), \ + .mod_vendor = (u32)UB_ANY_ID, .module = (u32)UB_ANY_ID + +#define UB_ENTITY_DRIVER_OVERRIDE(v, d, driver_override) \ + .vendor = (v), .device = (d), \ + .mod_vendor = (u32)UB_ANY_ID, .module = (u32)UB_ANY_ID, \ + .override_only = (driver_override) + +#define UB_DRIVER_OVERRIDE_ENTITY_VFIO(v, d) \ + UB_ENTITY_DRIVER_OVERRIDE(v, d, UB_ID_F_VFIO_DRIVER_OVERRIDE) + +#define UB_ENTITY_MODULE(v, d, m_v, m) \ + .vendor = (v), .device = (d), \ + .mod_vendor = (m_v), .module = (m) + +#define UB_ENTITY_CLASS(dev_class, dev_class_mask) \ + .vendor = (u32)UB_ANY_ID, .device = (u32)UB_ANY_ID, \ + .mod_vendor = (u32)UB_ANY_ID, .module = (u32)UB_ANY_ID, \ + .class_code = (dev_class), .class_mask = (dev_class_mask) + +#define ub_resource_start(dev, resno) ((dev)->zone[(resno)].res.start) +#define ub_resource_end(dev, resno) ((dev)->zone[(resno)].res.end) +#define ub_resource_flags(dev, resno) ((dev)->zone[(resno)].res.flags) +#define ub_resource_len(dev, resno) \ + ((ub_resource_start((dev), (resno)) == 0 && \ + ub_resource_end((dev), (resno)) == \ + ub_resource_start((dev), (resno))) ? 0 : \ + (ub_resource_end((dev), (resno)) - \ + ub_resource_start((dev), (resno)) + 1)) + +typedef unsigned int __bitwise ub_channel_state_t; +enum { + ub_channel_io_normal = (__force ub_channel_state_t) 1, + ub_channel_io_frozen = (__force ub_channel_state_t) 2, + ub_channel_io_perm_failure = (__force ub_channel_state_t) 3, +}; + +typedef unsigned int __bitwise ub_ers_result_t; +enum ub_ers_result { + UB_ERS_RESULT_NONE = (__force ub_ers_result_t) 1, + UB_ERS_RESULT_CAN_RECOVER = (__force ub_ers_result_t) 2, + UB_ERS_RESULT_NEED_RESET = (__force ub_ers_result_t) 3, + UB_ERS_RESULT_DISCONNECT = (__force ub_ers_result_t) 4, + UB_ERS_RESULT_RECOVERED = (__force ub_ers_result_t) 5, + UB_ERS_RESULT_NO_ERR_DRIVER = (__force ub_ers_result_t) 6, +}; + +#define UB_GUID_DW_NUM SZ_4 + +struct ub_bus_region { + unsigned long start; + unsigned long size; +}; + +struct mmio_zone { + struct resource res; + struct ub_bus_region region; + u8 ubba_used; + u8 sa_used; + u8 init_succ; + u8 decoder_mapped; +}; + +struct ub_guid { + union { + struct { + unsigned long long seq_num; + unsigned int reserved : 24; + unsigned int type : 4; + unsigned int version : 4; + unsigned int device : 16; + unsigned int vendor : 16; + } bits; + + guid_t id; + + unsigned int dw[UB_GUID_DW_NUM]; + }; +}; + +#define uent_vendor(uent) ((uent)->guid.bits.vendor) +#define uent_type(uent) ((uent)->guid.bits.type) +#define uent_version(uent) ((uent)->guid.bits.version) +#define uent_device(uent) ((uent)->guid.bits.device) +#define uent_class(uent) ((uent)->class_code) +#define uent_base_code(uent) ((uent)->class_code & UB_BASE_CODE_MASK) +#define uent_seq(uent) ((uent)->guid.bits.seq_num) + +enum ub_type { + UB_TYPE_BUS_INSTANCE = 0, + UB_TYPE_CONTROLLER = 1, + UB_TYPE_ICONTROLLER = 2, + UB_TYPE_SWITCH = 3, + UB_TYPE_ISWITCH = 4 +}; + +static inline int ub_show_guid(struct ub_guid *guid, char *buf) +{ + return sprintf(buf, "%04x-%04x-%01x-%01x-%06x-%016llx", + guid->bits.vendor, guid->bits.device, + guid->bits.version, guid->bits.type, + guid->bits.reserved, guid->bits.seq_num); +} + +#define MAX_UB_RES_NUM 3 + +enum ub_port_type { + PHYSICAL, + VIRTUAL, +}; + +#define UB_MAX_CNA_NUM SZ_64K +#define UB_PORT_CAP_NUM SZ_256 + +enum ub_link_state { + LINK_STATE_NORMAL = 0, + LINK_STATE_RESETING = 1, + LINK_STATE_DONE = 2, +}; + +struct ub_port { + struct ub_entity *uent; + u16 index; + enum ub_port_type type; + u8 domain_boundary; + bool shareable; + u32 cna; + struct ub_entity *r_uent; /* If valid, also represent link up */ + u16 r_index; + guid_t r_guid; + struct kobject kobj; + DECLARE_BITMAP(cna_maps, UB_MAX_CNA_NUM); + /* hotplug info */ + struct ub_slot *slot; + /* cap cache */ + DECLARE_BITMAP(cap_map, UB_PORT_CAP_NUM); + + struct work_struct link_work; + enum ub_link_state link_state; + u8 link_event; + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) +}; + +struct ue_map { + u16 start_entity_idx; + u16 end_entity_idx; +}; + +struct ub_entity { + /* Driver framework base info */ + struct device dev; + struct ub_driver *driver; + bool match_driver; /* Skip attaching driver before dev ready */ + const char *driver_override; /* Driver name to force a match */ + unsigned long priv_flags; /* Private flags for the UB driver */ + + /* entity base info */ + bool pool; + int ent_type; + struct ub_guid guid; + u16 class_code; + u16 mod_vendor; /* entity's module vendor and module id */ + u16 module; + unsigned int cna; + unsigned int eid; + unsigned short entity_idx; + u32 uent_num; /* ub dev number */ + u32 fm_cna; + struct mmio_zone zone[MAX_UB_RES_NUM]; + unsigned int total_funcs; + u32 token_id; + u32 token_value; + + /* MUE & UE info */ + u8 is_mue; + u16 total_ues; + u16 num_ues; + struct ue_map uem; + struct list_head mue_list; /* management ub entity list */ + struct list_head ue_list; /* entity list in management ub entity */ + u8 is_vdm_idev; + + /* entity topology info */ + struct list_head node; + struct ub_bus_controller *ubc; + struct ub_entity *pue; /* UE/MUE connected to their MUE */ + int topo_rank; /* The levels of Breadth-First Search */ + + /* entity port info */ + u16 port_nums; + struct ub_port *ports; + + /* entity capability info */ + unsigned int cfg0_bitmap[8]; + unsigned int cfg1_bitmap[8]; + + unsigned int state_saved : 1; + + /* entity DMA info */ + u64 dma_mask; + struct device_dma_parameters dma_parms; + + /* entity user interface */ + struct bin_attribute *res_attr[MAX_UB_RES_NUM]; /* sysfs file for resources */ + /* sysfs file for WC mapping of resources */ + struct bin_attribute *res_attr_wc[MAX_UB_RES_NUM]; + + /* UB interrupt info */ + raw_spinlock_t usi_lock; + unsigned int no_intr : 1; + unsigned int intr_enabled : 1; + unsigned int intr_type1 : 1; + void __iomem *intr_addr_base; + void __iomem *intr_vector_base; + u32 intr_device_id; + const struct attribute_group **msi_irq_groups; + + /* UB reset info */ + unsigned int reset_fn : 1; + + /* entity route info */ + struct list_head cna_list; /* store distance for cna in route table */ + + /* entity slot info */ + struct list_head slot_list; /* store slots under this dev */ + + struct dev_message *message; + + /* UB entity TID */ + u32 tid; + + bool sw_cap; + + /* UB saved config space */ + u32 saved_config_space[24]; /* Config space saved at reset time */ + + /* entity bus instance info */ + struct mutex instance_lock; + struct list_head instance_node; + struct ub_bus_instance *bi; + u32 user_eid; + struct ub_eu_table *eu_table; + + struct mutex active_mutex; + + u32 support_feature; + + u16 upi; + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) + CK_KABI_RESERVE(5) + CK_KABI_RESERVE(6) + CK_KABI_RESERVE(7) + CK_KABI_RESERVE(8) + CK_KABI_RESERVE(9) + CK_KABI_RESERVE(10) + CK_KABI_RESERVE(11) + CK_KABI_RESERVE(12) + CK_KABI_RESERVE(13) + CK_KABI_RESERVE(14) + CK_KABI_RESERVE(15) + CK_KABI_RESERVE(16) +}; + +/* UB bus error event callbacks */ +struct ub_error_handlers { + /* UB function reset prepare or completed */ + void (*ub_reset_prepare)(struct ub_entity *uent); + void (*ub_reset_done)(struct ub_entity *uent); + ub_ers_result_t (*ub_error_detected)(struct ub_entity *uent, ub_channel_state_t state); + ub_ers_result_t (*ub_resource_enabled)(struct ub_entity *uent); + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) +}; + +struct ub_dynids { + spinlock_t lock; /* Protects list, index */ + struct list_head list; /* For IDs added at runtime */ +}; + +#define to_ub_entity(n) container_of(n, struct ub_entity, dev) + +/** + * struct ub_driver - UB driver structure + * @node: List of driver structures. + * @name: Driver name. + * @id_table: Pointer to table of device IDs the driver is + * interested in. Most drivers should export this + * table using MODULE_DEVICE_TABLE(ub,...). + * @probe: This probing function gets called (during execution + * of ub_register_driver() for already existing + * entities or later if a new entity gets inserted) for + * all UB entities which match the ID table and are not + * "owned" by the other drivers yet. This function gets + * passed a "struct ub_entity *" for each entity whose + * entry in the ID table matches the entity. The probe + * function returns zero when the driver chooses to + * take "ownership" of the entity or an error code + * (negative number) otherwise. + * The probe function always gets called from process + * context, so it can sleep. + * @remove: The remove() function gets called whenever an entity + * being handled by this driver is removed (either during + * deregistration of the driver or when it's manually + * removed from a hot-pluggable slot). + * The remove function always gets called from process + * context, so it can sleep. + * @shutdown: Hook into reboot_notifier_list (kernel/sys.c). + * Intended to stop any idling operations. + * @virt_configure: Optional driver callback to allow configuration of + * UEs. This function is called to enable or disable UEs. + * @virt_notify: Optional driver callback to notify the driver about + * changes in UE status. This function is called + * when the status of a UE changes. + * @activate: Activate a specific entity. This function is called to + * activate an entity by its index. + * @deactivate: Deactivate a specific entity. This function is called to + * deactivate an entity by its index. + * @err_handler: Error handling callbacks. + * @groups: Sysfs attribute groups. + * @dev_groups: Attributes attached to the device that will be + * created once it is bound to the driver. + * @driver: Driver model structure. + * @dynids: List of dynamically added device IDs. + * @driver_managed_dma: Device driver doesn't use kernel DMA API for DMA. + * For most device drivers, no need to care about this flag + * as long as all DMAs are handled through the kernel DMA API. + * For some special ones, for example VFIO drivers, they know + * how to manage the DMA themselves and set this flag so that + * the IOMMU layer will allow them to setup and manage their + * own I/O address space. + */ +struct ub_driver { + struct list_head node; + const char *name; + const struct ub_device_id *id_table; /* Must be non-NULL for probe to be called */ + /* New entity inserted */ + int (*probe)(struct ub_entity *uent, const struct ub_device_id *id); + /* entity removed (NULL if not a hot-plug capable driver) */ + void (*remove)(struct ub_entity *uent); + void (*shutdown)(struct ub_entity *uent); + int (*virt_configure)(struct ub_entity *uent, int entity_idx, bool is_en); + int (*virt_notify)(struct ub_entity *uent, int entity_idx, bool is_en); + int (*activate)(struct ub_entity *uent, u32 entity_idx); + int (*deactivate)(struct ub_entity *uent, u32 entity_idx); + const struct ub_error_handlers *err_handler; + const struct attribute_group **groups; + const struct attribute_group **dev_groups; + struct device_driver driver; + struct ub_dynids dynids; + bool driver_managed_dma; + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) + CK_KABI_RESERVE(5) + CK_KABI_RESERVE(6) + CK_KABI_RESERVE(7) + CK_KABI_RESERVE(8) +}; + +struct ubc_common_attr { + u32 int_id_start; + u32 int_id_end; + u64 hpa_base; + u64 hpa_size; + u8 mem_size_limit; + u8 dma_cca; + u16 ummu_map; + u16 proximity_domain; + u64 queue_addr; + u64 queue_size; + u16 queue_depth; + u16 msg_int; + u8 msg_int_attr; + u64 ubc_guid_low; + u64 ubc_guid_high; +}; + +struct ub_bus_controller { + struct device dev; + struct ub_entity *uent; + struct ubc_common_attr attr; + u32 queue_virq; + char name[16]; + + u32 ctl_no; + struct message_device *mdev; + struct ub_decoder *decoder; + struct list_head resources; + struct list_head node; + struct list_head devs; + struct ub_bus_controller_ops *ops; + bool cluster; + struct ub_bus_instance *bi; + struct ub_bus_instance *cluster_bi; + + /* ub memory decoder */ + struct ub_mem_device *mem_device; + + void *data; + struct dentry *debug_root; + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) + CK_KABI_RESERVE(3) + CK_KABI_RESERVE(4) +}; + +struct ub_bus_instance_info { + u8 type; + u16 upi; + u32 eid : 20; + struct ub_guid guid; +}; + +struct ub_bus_instance { + bool registered; + bool destroy; + struct list_head node; + struct kref kref; + + struct ub_bus_instance_info info; + + struct ub_bus_controller *major; + + struct list_head uents; + struct mutex lock; + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) +}; + +#define ub_bi_is_dynamic(bi) ((bi)->info.type == UBUS_INSTANCE_DYNAMIC_SERVER \ + || (bi)->info.type == UBUS_INSTANCE_DYNAMIC_CLUSTER) + +static inline struct ub_driver *to_ub_driver(struct device_driver *drv) +{ + return drv ? container_of(drv, struct ub_driver, driver) : NULL; +} + +#define ub_err(pue, fmt, arg...) dev_err(&(pue)->dev, fmt, ##arg) +#define ub_info(pue, fmt, arg...) dev_info(&(pue)->dev, fmt, ##arg) +#define ub_warn(pue, fmt, arg...) dev_warn(&(pue)->dev, fmt, ##arg) +#define ub_dbg(pue, fmt, arg...) dev_dbg(&(pue)->dev, fmt, ##arg) + +static inline const char *ub_name(const struct ub_entity *pue) +{ + return dev_name(&pue->dev); +} + +/* interfaces for shareable port */ +enum ub_port_event { + UB_PORT_EVENT_LINK_DOWN, + UB_PORT_EVENT_LINK_UP, + UB_PORT_EVENT_RESET_PREPARE, + UB_PORT_EVENT_RESET_DONE +}; + +struct ub_share_port_ops { + void (*reset_prepare)(struct ub_entity *uent, u16 port_id); + void (*reset_done)(struct ub_entity *uent, u16 port_id); + void (*event_notify)(struct ub_entity *uent, u16 port_id, int event); + + CK_KABI_RESERVE(1) + CK_KABI_RESERVE(2) +}; + +struct ub_vdm_pld { + void *req_pld; + u16 req_pld_len; + void *rsp_pld; + u16 rsp_pld_len; + u16 rsp_buf_len; + u8 sub_msg_code; +}; + +#ifdef CONFIG_UB_UBUS +extern struct bus_type ub_bus_type; +#define dev_is_ub(d) ((d)->bus == &ub_bus_type) + +void ub_iommu_fwnode_handle_set(const struct fwnode_handle *fwnode); +const struct fwnode_handle *ub_iommu_fwnode_handle_get(void); + +/** + * ub_get_ent_by_eid() - Searching for UB entity by EID. + * @eid: entity EID. + * + * Traverse the UB bus device linked list and search for the device with + * the target EID. You need to call ub_entity_put() after using it. + * + * Context: Any context. + * Return: The entity found, or NULL if not found. + */ +struct ub_entity *ub_get_ent_by_eid(unsigned int eid); + +/** + * ub_get_ent_by_uent_num() - Searching for UB entities by entity Num. + * @uent_num: entity Num. + * + * Traverse the UB bus device linked list and search for the entity with + * the target entity Num. You need to call ub_entity_put() after using it. + * + * Context: Any context. + * Return: The entity found, or NULL if not found. + */ +struct ub_entity *ub_get_ent_by_uent_num(unsigned int uent_num); + +/** + * ub_get_ent_by_guid() - Searching for UB entities by GUID. + * @guid: GUID. + * + * Traverse the UB bus entity linked list and search for the entity with + * the target GUID. You need to call ub_entity_put() after using it. + * + * Context: Any context. + * Return: The entity found, or NULL if not found. + */ +struct ub_entity *ub_get_ent_by_guid(const struct ub_guid *guid); + +/** + * ub_entity_enable() - Enable or disable ub entity + * @uent: UB entity. + * @enable: Enable or disable. + * + * Enable or disable the communication channel between entity and user host. + * + * Context: Any context, it will take mutex_lock()/mutex_unlock(). + */ +void ub_entity_enable(struct ub_entity *uent, u8 enable); + +/** + * ub_set_user_info() - Initialize host information for the entity. + * @uent: UB entity. + * + * Initialize necessary host information for the entity for communication + * purposes, such as the host EID, CNA, and token ID. + * + * Context: Any context. + */ +int ub_set_user_info(struct ub_entity *uent); + +/** + * ub_unset_user_info() - Deinitialize host information for the entity. + * @uent: UB entity. + * + * Clearing the host information of an entity. + * + * Context: Any context. + */ +void ub_unset_user_info(struct ub_entity *uent); + +/** + * ub_disable_entities() - Disable UEs of MUE in batches. + * @pue: UB MUE. + * + * Remove all enabled UEs under the MUE from the system. + * + * Context: Any context. + */ +void ub_disable_entities(struct ub_entity *pue); + +/** + * ub_enable_ue() - Enable a single ue. + * @pue: UB MUE. + * @entity_idx: Number of the entity to be enabled. + * + * Create a specified UE under MUE, initialize the ue, + * and add it to the system. + * + * Context: Any context. + * Return: 0 if success, or %-EINVAL if @pue type is not MUE or @entity_idx + * is no longer in the UE range of MUE, or %-EEXIST if entity has been + * enabled, or other failed negative values. + */ +int ub_enable_ue(struct ub_entity *pue, int entity_idx); + +/** + * ub_disable_ue() - Disable a single ue. + * @pue: UB MUE. + * @entity_idx: Number of the entity to be disabled. + * + * Remove a specified UE. + * + * Context: Any context. + * Return: 0 if success, or %-EINVAL if @pue type is not MUE or @entity_idx + * is no longer in the UE range of MUE, or %-ENODEV if entity hasn't + * been enabled. + */ +int ub_disable_ue(struct ub_entity *pue, int entity_idx); + +/** + * ub_get_dst_eid() - Obtain the Dest EID of the entity. + * @uent: UB entity. + * + * Return the EID of bus instance if the entity has already been bound, + * otherwise return controller's EID. + * + * Context: Any context. + * Return: positive number if success, or %-EINVAL if @dev is %NULL, + * or %-ENODEV if entity's controller %NULL, or 0 if entity hasn't been + * initialized. + */ +int ub_get_dst_eid(struct ub_entity *uent); + +/** + * ub_iomap() - Map the resource space of the entity. + * @uent: UB entity. + * @resno: Resource Number. + * @maxlen: Map size. + * + * Invoke ioremap() to map the entity resource space, if maxlen is 0, then map + * size is ub_resource_len(), else map size is min(maxlen, ub_resource_len()). + * + * Context: Any context. + * Return: The return value is the same as that of ioremap(). + */ +void __iomem *ub_iomap(struct ub_entity *uent, int resno, unsigned long maxlen); + +/** + * ub_iomap_wc() - Map the resource space of the entity. + * @uent: UB entity. + * @resno: Resource Number. + * @maxlen: Map size. + * + * Invoke ioremap_wc() to map the entity resource space, if maxlen is 0, + * then map size is ub_resource_len(), else map size is + * min(maxlen, ub_resource_len()). + * + * Context: Any context. + * Return: The return value is the same as that of ioremap_wc(). + */ +void __iomem *ub_iomap_wc(struct ub_entity *uent, int resno, unsigned long maxlen); + +/** + * ub_iounmap() - Unmap the resource space of the entity. + * @addr: Target resource address. + * + * Invoke iounmap() to unmap the entity resource space. + * + * Context: Any context. + */ +void ub_iounmap(void __iomem *addr); + +/** + * ub_register_share_port() - Register a share port. + * @uent: UB entity. + * @port_id: UB Bus Controller port id. + * @ops: UB share port ops. + * + * The IDEV reuses the physical port of the ub bus controller. Record the + * callback function. + * + * Context: Any context + * Return: 0 if success, or %-ENOMEM if system out of memory, + * or %-EINVAL if parameters invalid. + */ +int ub_register_share_port(struct ub_entity *uent, u16 port_id, + struct ub_share_port_ops *ops); + +/** + * ub_unregister_share_port() - Unregister a share port. + * @uent: UB entity. + * @port_id: UB Bus Controller port id. + * @ops: UB share port ops. + * + * Clear the callback function. + * + * Context: Any context + */ +void ub_unregister_share_port(struct ub_entity *uent, u16 port_id, + struct ub_share_port_ops *ops); + +/** + * ub_reset_entity() - Function entity level reset. + * @ent: UB entity. + * + * Reset a single entity without affecting other entities. If you want to reuse + * the entity after reset, you need to re-initialize it. + * + * Context: Any context + * Return: 0 if success, or %-EINVAL if entity not support elr, + * or %-ENOTTY if entity can't be reset safely, + * or -EBUSY if can't get device_trylock(), or other failed negative values. + */ +int ub_reset_entity(struct ub_entity *ent); + +/** + * ub_device_reset() - Device level reset. + * @ent: UB entity. + * + * Reset device, including all entities under the device. If you want to reuse + * the device after reset, you need to re-initialize it. + * + * Context: Any context + * Return: 0 if success, or %-EINVAL if parameters invalid, + * or %-EIO if device can't reset now, can try later. + */ +int ub_device_reset(struct ub_entity *ent); + +/** + * ub_vdm_message() - Send vendor private message. + * @uent: UB entity. + * @vdm_pld: Vendor private message payload context. + * + * Send a vendor private message to the entity. Response will be put in + * vdm_pld->rsp_pld, and response length is stored in vdm->rsp_pld_len. + * + * Context: Any context, it will take spin_lock_irqsave()/spin_unlock_restore() + * Return: 0 if success, or %-EINVAL if parameters invalid, + * or %-ENOMEM if system out of memory, or other failed negative values. + */ +int ub_vdm_message(struct ub_entity *uent, struct ub_vdm_pld *vdm_pld); + +/* Only for ubus module */ +unsigned int ub_irq_calc_affinity_vectors(unsigned int minvec, + unsigned int maxvec, + const struct irq_affinity *affd); + +/** + * ub_disable_intr() - Free entity interrupt vectors. + * @uent: UB entity. + * + * Free interrupt vectors of the device. + * + * Context: Any context. + */ +void ub_disable_intr(struct ub_entity *uent); + +/** + * ub_intr_vec_count() - Interrupt Vectors Supported by an entity. + * @uent: UB entity. + * + * Querying the Number of Interrupt Vectors Supported by an entity. + * For interrupt type 2. + * + * Context: Any context. + * Return: Number of Interrupts Supported if success, or 0 if failed. + */ +u32 ub_intr_vec_count(struct ub_entity *uent); + +/** + * ub_int_type1_vec_count() - Interrupt Vectors Supported by an entity. + * @uent: UB entity. + * + * Querying the Number of Interrupt Vectors Supported by an entity. + * For interrupt type 1. + * + * Context: Any context. + * Return: Number of Interrupts Supported if success, or 0 if failed. + */ +u32 ub_int_type1_vec_count(struct ub_entity *uent); + +#define UB_IRQ_AFFINITY (1 << 0) /* Auto-assign affinity */ +/** + * ub_alloc_irq_vectors_affinity() - Allocate multiple entity interrupt vectors. + * @uent: UB entity. + * @min_vecs: minimum required number of vectors (must be >= 1). + * @max_vecs: maximum desired number of vectors. + * + * @flags: allocation flags(can be 0): + * + * * %UB_IRQ_AFFINITY Auto-manage IRQs affinity by spreading + * the vectors around available CPUs + * + * @affd: affinity requirements (can be %NULL). + * + * Allocate interrupt vectors for the entity and set the affinity. + * + * Context: Any context. + * Return: the number of vectors allocated (which might be smaller than + * @max_vecs) if success, or a negative error code on error. If less than + * @min_vecs interrupt vectors are available for @uent the function will + * fail with -ENOSPC + */ +int ub_alloc_irq_vectors_affinity(struct ub_entity *uent, unsigned int min_vecs, + unsigned int max_vecs, unsigned int flags, + struct irq_affinity *affd); +static inline int ub_alloc_irq_vectors(struct ub_entity *uent, + unsigned int min_vecs, + unsigned int max_vecs) +{ + return ub_alloc_irq_vectors_affinity(uent, min_vecs, max_vecs, 0, NULL); +} + +/** + * ub_irq_vector() - Obtaining the Linux IRQ number. + * @uent: UB entity. + * @nr: Interrupt vector. + * + * Translate from Interrupt Vectors to Linux IRQ number. + * + * Context: Any context. + * Return: IRQ number if success, or %-EINVAL if failed. + */ +int ub_irq_vector(struct ub_entity *uent, unsigned int nr); + +/** + * ub_irq_get_affinity() - Get an entity interrupt vector affinity + * @uent: the UB entity to operate on + * @nr: entity-relative interrupt vector index (0-based); has different + * meanings, depending on interrupt mode: + * + * * INTR_TYPE2 the index in the USI vector table + * * INTR_TYPE1 the index of the enabled USI vectors + * + * Return: USI vector affinity, NULL if @nr is out of range or if + * the USI vector was allocated without explicit affinity + * requirements (e.g., by ub_alloc_irq_vectors(), or + * ub_alloc_irq_vectors_affinity() without the %UB_IRQ_AFFINITY flag). + */ +const struct cpumask *ub_irq_get_affinity(struct ub_entity *uent, int nr); + +/** + * ub_activate_entity() - Activate entity. + * @uent: UB entity. + * @entity_idx: Number of the entity to be activated. + * + * Context: Any context, it will take mutex_lock()/mutex_unlock() + * Return: 0 if success, or %-EINVAL if the device doesn't match the driver, + * or other failed negative values. + */ +int ub_activate_entity(struct ub_entity *uent, u32 entity_idx); + +/** + * ub_deactivate_entity() - Deactivate entity. + * @uent: UB entity. + * @entity_idx: Number of the entity to be deactivated. + * + * Context: Any context, it will take mutex_lock()/mutex_unlock() + * Return: 0 if success, or %-EINVAL if the entity doesn't match the driver, + * or other failed negative values. + */ +int ub_deactivate_entity(struct ub_entity *uent, u32 entity_idx); + +/** + * ub_cfg_read_byte() - 1 byte configuration access read. + * @uent: UB entity. + * @pos: Config space address. + * @val: Output buffer. + * + * Initiate configuration access to the specified address of the entity + * configuration space and read 1 byte. + * + * Context: Any context, it will take spin_lock_irqsave()/spin_unlock_restore() + * Return: 0 if success, or negative value if failed. + */ +int ub_cfg_read_byte(struct ub_entity *uent, u64 pos, u8 *val); +int ub_cfg_read_word(struct ub_entity *uent, u64 pos, u16 *val); +int ub_cfg_read_dword(struct ub_entity *uent, u64 pos, u32 *val); +/** + * ub_cfg_write_byte() - 1 byte configuration access write. + * @uent: UB entity. + * @pos: Config space address. + * @val: Data. + * + * Initiate configuration access to the specified address of the entity + * configuration space and write 1 byte. + * + * Context: Any context, it will take spin_lock_irqsave()/spin_unlock_restore() + * Return: 0 if success, or negative value if failed. + */ +int ub_cfg_write_byte(struct ub_entity *uent, u64 pos, u8 val); +int ub_cfg_write_word(struct ub_entity *uent, u64 pos, u16 val); +int ub_cfg_write_dword(struct ub_entity *uent, u64 pos, u32 val); + +/** + * ub_entity_get() - Atomically increment the reference count for the entity. + * @uent: UB entity pointer. + * + * Context: Any context. + * Return: @uent itself, or NULL if @uent is NULL. + */ +struct ub_entity *ub_entity_get(struct ub_entity *uent); + +/** + * ub_entity_put() - decrement the reference count for the entity. + * @uent: UB entity pointer. + * + * Context: Any context. + */ +void ub_entity_put(struct ub_entity *uent); + +/** + * ub_get_bus_controller() - Obtains the ub bus controller entity list. + * @uents: Output buffer for the UBC entities list. + * @max_num: Buffer size. + * @real_num: Real entities num. + * + * All ub bus controllers in the system are collected in @uents. Increase the + * reference counting of all entities by 1. Remember to call + * ub_put_bus_controller() after using it. + * + * Context: Any context. + * Return: 0 if success, or %-EINVAL if input parameter is NULL, + * or %-ENOMEM if buffer is too small. + */ +int ub_get_bus_controller(struct ub_entity *uents[], unsigned int max_num, + unsigned int *real_num); + +/** + * ub_put_bus_controller() - Free the ub bus controller device list. + * @uents: UBC entities list + * @num: entities num + * + * Decrement the reference counting of all entities by 1. + * + * Context: Any context. + */ +void ub_put_bus_controller(struct ub_entity *uents[], unsigned int num); + +/** + * ub_cc_supported() - Congestion control capability query. + * @uent: UB entity. + * + * Context: Any context. + * Return: %true if the entity supports congestion control, %false otherwise. + */ +bool ub_cc_supported(struct ub_entity *uent); + +/** + * ub_cc_enable() - Enable the congestion control capability of the entity. + * @uent: UB entity. + * + * Context: Any context. + * Return: 0 if success, or %-EINVAL if input parameters are invalid, or + * %-EPERM if the entity does not support congestion control, or other + * failed negative values. + */ +int ub_cc_enable(struct ub_entity *uent); + +/** + * ub_cc_disable() - Disable the congestion control capability of the entity. + * @uent: UB entity. + * + * Context: Any context. + * Return: 0 if success, or %-EINVAL if input parameters are invalid, or + * %-EPERM if the entity does not support congestion control, or other + * failed negative values. + */ +int ub_cc_disable(struct ub_entity *uent); + +/* Proper probing supporting hot-pluggable entities */ +int __ub_register_driver(struct ub_driver *drv, struct module *owner, + const char *mod_name); +/* ub_register_driver() must be a macro so KBUILD_MODNAME can be expanded */ +#define ub_register_driver(driver) \ + __ub_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) +void ub_unregister_driver(struct ub_driver *drv); + +/** + * ub_stop_ent() - Stop the entity. + * @uent: UB entity. + * + * Call device_release_driver(), user can't use it again. If it's a MUE, + * will stop all UEs under it. If it's entity0, will stop all entities under it. + * + * Context: Any context. + */ +void ub_stop_ent(struct ub_entity *uent); + +/** + * ub_stop_and_remove_ent() - Stop and remove the entity from system. + * @uent: UB entity. + * + * Call device_release_driver() and device_unregister(). If it's a MUE, + * will remove all UEs under it. If it's entity0, will remove all entities + * under it. + * + * Context: Any context. + */ +void ub_stop_and_remove_ent(struct ub_entity *uent); + +#else /* CONFIG_UB_UBUS is not enabled */ +#define dev_is_ub(d) (false) +static inline struct ub_entity *ub_get_ent_by_eid(unsigned int eid) +{ return NULL; } +static inline struct ub_entity *ub_get_ent_by_uent_num(unsigned int uent_num) +{ return NULL; } +static inline struct ub_entity *ub_get_ent_by_guid(const struct ub_guid *guid) +{ return NULL; } +static inline struct ub_entity *ub_entity_get(struct ub_entity *uent) +{ return NULL; } +static inline void ub_entity_put(struct ub_entity *uent) {} +static inline void ub_entity_enable(struct ub_entity *uent, u8 enable) {} +static inline int ub_set_user_info(struct ub_entity *uent) +{ return -ENODEV; } +static inline void ub_unset_user_info(struct ub_entity *uent) {} +static inline void ub_disable_entities(struct ub_entity *pue) {} +static inline int ub_enable_ue(struct ub_entity *pue, int entity_idx) +{ return -ENODEV; } +static inline int ub_disable_ue(struct ub_entity *pue, int entity_idx) +{ return -ENODEV; } +static inline int ub_get_dst_eid(struct ub_entity *uent) +{ return -ENODEV; } +static inline void __iomem * +ub_iomap(struct ub_entity *uent, int resno, unsigned long maxlen) +{ return NULL; } +static inline void __iomem * +ub_iomap_wc(struct ub_entity *uent, int resno, unsigned long maxlen) +{ return NULL; } +static inline void ub_iounmap(void __iomem *addr) {} +static inline bool ub_cc_supported(struct ub_entity *uent) +{ return false; } +static inline int ub_cc_enable(struct ub_entity *uent) +{ return -ENODEV; } +static inline int ub_cc_disable(struct ub_entity *uent) +{ return -ENODEV; } +static inline int ub_cfg_read_byte(struct ub_entity *uent, u64 pos, u8 *val) +{ return -ENODEV; } +static inline int ub_cfg_read_word(struct ub_entity *uent, u64 pos, u16 *val) +{ return -ENODEV; } +static inline int ub_cfg_read_dword(struct ub_entity *uent, u64 pos, u32 *val) +{ return -ENODEV; } +static inline int ub_cfg_write_byte(struct ub_entity *uent, u64 pos, u8 val) +{ return -ENODEV; } +static inline int ub_cfg_write_word(struct ub_entity *uent, u64 pos, u16 val) +{ return -ENODEV; } +static inline int ub_cfg_write_dword(struct ub_entity *uent, u64 pos, u32 val) +{ return -ENODEV; } +static inline int ub_get_bus_controller(struct ub_entity *uents[], + unsigned int max_num, + unsigned int *real_num) +{ return -ENODEV; } +static inline void +ub_put_bus_controller(struct ub_entity *uents[], unsigned int num) {} +static inline void ub_iommu_fwnode_handle_set(const struct fwnode_handle *fwnode) {} +static inline const struct fwnode_handle *ub_iommu_fwnode_handle_get(void) +{ return NULL; } +static inline int ub_register_share_port(struct ub_entity *uent, u16 port_id, + struct ub_share_port_ops *ops) +{ return -ENODEV; } +static inline void ub_unregister_share_port(struct ub_entity *uent, u16 port_id, + struct ub_share_port_ops *ops) {} +static inline int ub_reset_entity(struct ub_entity *uent) +{ return -ENODEV; } +static inline int ub_device_reset(struct ub_entity *uent) +{ return -ENODEV; } +static inline int ub_vdm_message(struct ub_entity *uent, + struct ub_vdm_pld *vdm_pld) +{ return -ENODEV; } +static inline unsigned int +ub_irq_calc_affinity_vectors(unsigned int minvec, unsigned int maxvec, + const struct irq_affinity *affd) +{ + return maxvec; +} +static inline void ub_disable_intr(struct ub_entity *uent) {} +static inline u32 ub_intr_vec_count(struct ub_entity *uent) +{ return 0; } +static inline u32 ub_int_type1_vec_count(struct ub_entity *uent) +{ return 0; } +static inline int +ub_alloc_irq_vectors_affinity(struct ub_entity *uent, unsigned int min_vecs, + unsigned int max_vecs, unsigned int flags, + struct irq_affinity *affd) +{ return -ENODEV; } +static inline int ub_alloc_irq_vectors(struct ub_entity *uent, + unsigned int min_vecs, + unsigned int max_vecs) +{ return -ENODEV; } +static inline int ub_irq_vector(struct ub_entity *uent, unsigned int nr) +{ return -EINVAL; } +static inline const struct cpumask * +ub_irq_get_affinity(struct ub_entity *uent, int nr) { return cpu_possible_mask; } +static inline int ub_activate_entity(struct ub_entity *uent, u32 entity_idx) +{ return -ENODEV; } +static inline int ub_deactivate_entity(struct ub_entity *uent, u32 entity_idx) +{ return -ENODEV; } +static inline int +__ub_register_driver(struct ub_driver *drv, struct module *owner, + const char *mod_name) +{ return 0; } +static inline int ub_register_driver(struct ub_driver *drv) +{ return 0; } +static inline void ub_unregister_driver(struct ub_driver *drv) {} +static inline void ub_stop_ent(struct ub_entity *uent) {} +static inline void ub_stop_and_remove_ent(struct ub_entity *uent) {} +#endif /* CONFIG_UB_UBUS */ + +#endif /* _UB_UBUS_UBUS_H_ */ diff --git a/include/ub/ubus/ubus_ids.h b/include/ub/ubus/ubus_ids.h new file mode 100644 index 0000000000000000000000000000000000000000..62ead65e5061f24374b91d1fb7ca6b9c0a2c4e1d --- /dev/null +++ b/include/ub/ubus/ubus_ids.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) HiSilicon Technologies Co., Ltd. 2025. All rights reserved. + */ +#ifndef _UB_UBUS_UBUS_IDS_H_ +#define _UB_UBUS_UBUS_IDS_H_ + +/* + * Device Class base code and sub code + * +----------+-----------+ + * | sub code | base code | + * | [15:8] | [7:0] | + * +----------+-----------+ + */ + +#define UB_BASE_CODE_BUS_CONTROLLER 0x00 +#define UB_CLASS_BUS_CONTROLLER 0x0000 + +#define UB_BASE_CODE_STORAGE 0x01 +#define UB_CLASS_STORAGE_LPC 0x0001 +#define UB_CLASS_STORAGE_LBC 0x0101 + +#define UB_BASE_CODE_NETWORK 0x02 +#define UB_CLASS_NETWORK_UB 0x0002 +#define UB_CLASS_NETWORK_ETH 0x0102 + +#define UB_BASE_CODE_SWITCH 0x03 +#define UB_CLASS_SWITCH_UB 0x0003 + +#define UB_BASE_CODE_NPU 0x04 +#define UB_CLASS_NPU_UB 0x0004 + +#define UB_BASE_CODE_UNKNOWN 0xFF +#define UB_CLASS_UNKNOWN 0x00FF + +#endif /* _UB_UBUS_UBUS_IDS_H_ */ diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c index cc19a3efea8960e325ad22cc37cff49a03842ec8..23aeba267cfac8ff9e5011a59247c833794f8227 100644 --- a/kernel/dma/map_benchmark.c +++ b/kernel/dma/map_benchmark.c @@ -18,6 +18,9 @@ #include #include #include +#ifdef CONFIG_UB_UBUS +#include +#endif struct map_benchmark_data { struct map_benchmark bparam; @@ -351,6 +354,19 @@ static struct pci_driver map_benchmark_pci_driver = { .probe = map_benchmark_pci_probe, }; +#ifdef CONFIG_UB_UBUS +static int map_benchmark_ub_probe(struct ub_entity *uent, + const struct ub_device_id *id) +{ + return __map_benchmark_probe(&uent->dev); +} + +static struct ub_driver map_benchmark_ub_driver = { + .name = "dma_map_benchmark", + .probe = map_benchmark_ub_probe, +}; +#endif + static int __init map_benchmark_init(void) { int ret; @@ -360,16 +376,30 @@ static int __init map_benchmark_init(void) return ret; ret = platform_driver_register(&map_benchmark_platform_driver); - if (ret) { - pci_unregister_driver(&map_benchmark_pci_driver); - return ret; - } + if (ret) + goto err_reg_platform; + +#ifdef CONFIG_UB_UBUS + ret = ub_register_driver(&map_benchmark_ub_driver); + if (ret) + goto err_reg_ub; +#endif return 0; +#ifdef CONFIG_UB_UBUS +err_reg_ub: + platform_driver_unregister(&map_benchmark_platform_driver); +#endif +err_reg_platform: + pci_unregister_driver(&map_benchmark_pci_driver); + return ret; } static void __exit map_benchmark_cleanup(void) { +#ifdef CONFIG_UB_UBUS + ub_unregister_driver(&map_benchmark_ub_driver); +#endif platform_driver_unregister(&map_benchmark_platform_driver); pci_unregister_driver(&map_benchmark_pci_driver); } diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c index cb5bcfbab43ab96d174cf9bf20dedc61bbb02969..d5f01bdcf014283c22d1869f2d0e6b02454bf3c5 100644 --- a/kernel/irq/msi.c +++ b/kernel/irq/msi.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "internals.h" @@ -158,6 +160,7 @@ int msi_domain_insert_msi_desc(struct device *dev, unsigned int domid, /* Copy type specific data to the new descriptor. */ desc->pci = init_desc->pci; + desc->ub_intr = init_desc->ub_intr; return msi_insert_desc(dev, desc, domid, init_desc->msi_index); } @@ -448,7 +451,7 @@ unsigned int msi_domain_get_virq(struct device *dev, unsigned int domid, unsigne { struct msi_desc *desc; unsigned int ret = 0; - bool pcimsi = false; + bool devmsi = false; struct xarray *xa; if (!dev->msi.data) @@ -459,18 +462,23 @@ unsigned int msi_domain_get_virq(struct device *dev, unsigned int domid, unsigne /* This check is only valid for the PCI default MSI domain */ if (dev_is_pci(dev) && domid == MSI_DEFAULT_DOMAIN) - pcimsi = to_pci_dev(dev)->msi_enabled; + devmsi = to_pci_dev(dev)->msi_enabled; +#ifdef CONFIG_UB_UBUS + /* This check is only valid for the UBUS default MSI domain */ + if (dev_is_ub(dev) && domid == MSI_DEFAULT_DOMAIN) + devmsi = to_ub_entity(dev)->intr_type1; +#endif msi_lock_descs(dev); xa = &dev->msi.data->__domains[domid].store; - desc = xa_load(xa, pcimsi ? 0 : index); + desc = xa_load(xa, devmsi ? 0 : index); if (desc && desc->irq) { /* * PCI-MSI has only one descriptor for multiple interrupts. * PCI-MSIX and platform MSI use a descriptor per * interrupt. */ - if (pcimsi) { + if (devmsi) { if (index < desc->nvec_used) ret = desc->irq + index; } else { diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index abe65f8968dd59957b785483d457b0413640c73f..d445884800c8b3b50834ec2502c21dfd9bd115d1 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -267,5 +267,16 @@ int main(void) DEVID_FIELD(cdx_device_id, device); DEVID_FIELD(cdx_device_id, override_only); +#ifdef CONFIG_UB + DEVID(ub_device_id); + DEVID_FIELD(ub_device_id, vendor); + DEVID_FIELD(ub_device_id, device); + DEVID_FIELD(ub_device_id, mod_vendor); + DEVID_FIELD(ub_device_id, module); + DEVID_FIELD(ub_device_id, class_code); + DEVID_FIELD(ub_device_id, class_mask); + DEVID_FIELD(ub_device_id, override_only); +#endif + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index ea498eff1f2ae58e4faed16d873120e9faa3753d..ac15eeecd8c0595076879b884496e9330f103755 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1475,6 +1475,54 @@ static int do_cdx_entry(const char *filename, void *symval, return 1; } +#ifdef CONFIG_UB +/* Looks like: ub:vNdNmvNmNcN or _ub:vNdNmvNmNcN. */ +static int do_ub_entry(const char *filename, void *symval, char *alias) +{ + /* Class code field can be divided into these two. */ + unsigned char basecode_mask, subcode_mask; + + DEF_FIELD(symval, ub_device_id, vendor); + DEF_FIELD(symval, ub_device_id, device); + DEF_FIELD(symval, ub_device_id, mod_vendor); + DEF_FIELD(symval, ub_device_id, module); + DEF_FIELD(symval, ub_device_id, class_code); + DEF_FIELD(symval, ub_device_id, class_mask); + DEF_FIELD(symval, ub_device_id, override_only); + + switch (override_only) { + case 0: + strcpy(alias, "ub:"); + break; + case UB_ID_F_VFIO_DRIVER_OVERRIDE: + strcpy(alias, "vfio_ub:"); + break; + default: + warn("Unknown UB driver_override alias %08X\n", + override_only); + return 0; + } + + ADD(alias, "v", vendor != UB_ANY_ID, (__u16)vendor); + ADD(alias, "d", device != UB_ANY_ID, (__u16)device); + ADD(alias, "mv", mod_vendor != UB_ANY_ID, (__u16)mod_vendor); + ADD(alias, "m", module != UB_ANY_ID, (__u16)module); + + basecode_mask = class_mask; + subcode_mask = (class_mask) >> 8; + + if ((basecode_mask != 0 && basecode_mask != 0xFF) + || (subcode_mask != 0 && subcode_mask != 0xFF)) { + warn("Can't handle masks in %s:%04X\n", + filename, class_mask); + return 0; + } + + ADD(alias, "c", subcode_mask == 0xFF && basecode_mask == 0xFF, class_code); + return 1; +} +#endif + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { @@ -1555,6 +1603,9 @@ static const struct devtable devtable[] = { {"dfl", SIZE_dfl_device_id, do_dfl_entry}, {"ishtp", SIZE_ishtp_device_id, do_ishtp_entry}, {"cdx", SIZE_cdx_device_id, do_cdx_entry}, +#ifdef CONFIG_UB + {"ub", SIZE_ub_device_id, do_ub_entry}, +#endif }; /* Create MODULE_ALIAS() statements.