From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ... --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 944 +++++++++++++++++++++ 1 file changed, 944 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c new file mode 100644 index 000000000..19f543ba7 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -0,0 +1,944 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#include "dm_services_types.h" +#include "dc.h" + +#include "amdgpu.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_irq.h" + +/** + * DOC: overview + * + * DM provides another layer of IRQ management on top of what the base driver + * already provides. This is something that could be cleaned up, and is a + * future TODO item. + * + * The base driver provides IRQ source registration with DRM, handler + * registration into the base driver's IRQ table, and a handler callback + * amdgpu_irq_handler(), with which DRM calls on interrupts. This generic + * handler looks up the IRQ table, and calls the respective + * &amdgpu_irq_src_funcs.process hookups. + * + * What DM provides on top are two IRQ tables specifically for top-half and + * bottom-half IRQ handling, with the bottom-half implementing workqueues: + * + * - &amdgpu_display_manager.irq_handler_list_high_tab + * - &amdgpu_display_manager.irq_handler_list_low_tab + * + * They override the base driver's IRQ table, and the effect can be seen + * in the hooks that DM provides for &amdgpu_irq_src_funcs.process. They + * are all set to the DM generic handler amdgpu_dm_irq_handler(), which looks up + * DM's IRQ tables. However, in order for base driver to recognize this hook, DM + * still needs to register the IRQ with the base driver. See + * dce110_register_irq_handlers() and dcn10_register_irq_handlers(). + * + * To expose DC's hardware interrupt toggle to the base driver, DM implements + * &amdgpu_irq_src_funcs.set hooks. Base driver calls it through + * amdgpu_irq_update() to enable or disable the interrupt. + */ + +/****************************************************************************** + * Private declarations. + *****************************************************************************/ + +/** + * struct amdgpu_dm_irq_handler_data - Data for DM interrupt handlers. + * + * @list: Linked list entry referencing the next/previous handler + * @handler: Handler function + * @handler_arg: Argument passed to the handler when triggered + * @dm: DM which this handler belongs to + * @irq_source: DC interrupt source that this handler is registered for + * @work: work struct + */ +struct amdgpu_dm_irq_handler_data { + struct list_head list; + interrupt_handler handler; + void *handler_arg; + + struct amdgpu_display_manager *dm; + /* DAL irq source which registered for this interrupt. */ + enum dc_irq_source irq_source; + struct work_struct work; +}; + +#define DM_IRQ_TABLE_LOCK(adev, flags) \ + spin_lock_irqsave(&adev->dm.irq_handler_list_table_lock, flags) + +#define DM_IRQ_TABLE_UNLOCK(adev, flags) \ + spin_unlock_irqrestore(&adev->dm.irq_handler_list_table_lock, flags) + +/****************************************************************************** + * Private functions. + *****************************************************************************/ + +static void init_handler_common_data(struct amdgpu_dm_irq_handler_data *hcd, + void (*ih)(void *), + void *args, + struct amdgpu_display_manager *dm) +{ + hcd->handler = ih; + hcd->handler_arg = args; + hcd->dm = dm; +} + +/** + * dm_irq_work_func() - Handle an IRQ outside of the interrupt handler proper. + * + * @work: work struct + */ +static void dm_irq_work_func(struct work_struct *work) +{ + struct amdgpu_dm_irq_handler_data *handler_data = + container_of(work, struct amdgpu_dm_irq_handler_data, work); + + handler_data->handler(handler_data->handler_arg); + + /* Call a DAL subcomponent which registered for interrupt notification + * at INTERRUPT_LOW_IRQ_CONTEXT. + * (The most common use is HPD interrupt) */ +} + +/* + * Remove a handler and return a pointer to handler list from which the + * handler was removed. + */ +static struct list_head *remove_irq_handler(struct amdgpu_device *adev, + void *ih, + const struct dc_interrupt_params *int_params) +{ + struct list_head *hnd_list; + struct list_head *entry, *tmp; + struct amdgpu_dm_irq_handler_data *handler; + unsigned long irq_table_flags; + bool handler_removed = false; + enum dc_irq_source irq_source; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + irq_source = int_params->irq_source; + + switch (int_params->int_context) { + case INTERRUPT_HIGH_IRQ_CONTEXT: + hnd_list = &adev->dm.irq_handler_list_high_tab[irq_source]; + break; + case INTERRUPT_LOW_IRQ_CONTEXT: + default: + hnd_list = &adev->dm.irq_handler_list_low_tab[irq_source]; + break; + } + + list_for_each_safe(entry, tmp, hnd_list) { + + handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, + list); + + if (handler == NULL) + continue; + + if (ih == handler->handler) { + /* Found our handler. Remove it from the list. */ + list_del(&handler->list); + handler_removed = true; + break; + } + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + + if (handler_removed == false) { + /* Not necessarily an error - caller may not + * know the context. */ + return NULL; + } + + kfree(handler); + + DRM_DEBUG_KMS( + "DM_IRQ: removed irq handler: %p for: dal_src=%d, irq context=%d\n", + ih, int_params->irq_source, int_params->int_context); + + return hnd_list; +} + +/** + * unregister_all_irq_handlers() - Cleans up handlers from the DM IRQ table + * @adev: The base driver device containing the DM device + * + * Go through low and high context IRQ tables and deallocate handlers. + */ +static void unregister_all_irq_handlers(struct amdgpu_device *adev) +{ + struct list_head *hnd_list_low; + struct list_head *hnd_list_high; + struct list_head *entry, *tmp; + struct amdgpu_dm_irq_handler_data *handler; + unsigned long irq_table_flags; + int i; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + for (i = 0; i < DAL_IRQ_SOURCES_NUMBER; i++) { + hnd_list_low = &adev->dm.irq_handler_list_low_tab[i]; + hnd_list_high = &adev->dm.irq_handler_list_high_tab[i]; + + list_for_each_safe(entry, tmp, hnd_list_low) { + + handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, + list); + + if (handler == NULL || handler->handler == NULL) + continue; + + list_del(&handler->list); + kfree(handler); + } + + list_for_each_safe(entry, tmp, hnd_list_high) { + + handler = list_entry(entry, struct amdgpu_dm_irq_handler_data, + list); + + if (handler == NULL || handler->handler == NULL) + continue; + + list_del(&handler->list); + kfree(handler); + } + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +} + +static bool +validate_irq_registration_params(struct dc_interrupt_params *int_params, + void (*ih)(void *)) +{ + if (NULL == int_params || NULL == ih) { + DRM_ERROR("DM_IRQ: invalid input!\n"); + return false; + } + + if (int_params->int_context >= INTERRUPT_CONTEXT_NUMBER) { + DRM_ERROR("DM_IRQ: invalid context: %d!\n", + int_params->int_context); + return false; + } + + if (!DAL_VALID_IRQ_SRC_NUM(int_params->irq_source)) { + DRM_ERROR("DM_IRQ: invalid irq_source: %d!\n", + int_params->irq_source); + return false; + } + + return true; +} + +static bool validate_irq_unregistration_params(enum dc_irq_source irq_source, + irq_handler_idx handler_idx) +{ + if (DAL_INVALID_IRQ_HANDLER_IDX == handler_idx) { + DRM_ERROR("DM_IRQ: invalid handler_idx==NULL!\n"); + return false; + } + + if (!DAL_VALID_IRQ_SRC_NUM(irq_source)) { + DRM_ERROR("DM_IRQ: invalid irq_source:%d!\n", irq_source); + return false; + } + + return true; +} +/****************************************************************************** + * Public functions. + * + * Note: caller is responsible for input validation. + *****************************************************************************/ + +/** + * amdgpu_dm_irq_register_interrupt() - Register a handler within DM. + * @adev: The base driver device containing the DM device. + * @int_params: Interrupt parameters containing the source, and handler context + * @ih: Function pointer to the interrupt handler to register + * @handler_args: Arguments passed to the handler when the interrupt occurs + * + * Register an interrupt handler for the given IRQ source, under the given + * context. The context can either be high or low. High context handlers are + * executed directly within ISR context, while low context is executed within a + * workqueue, thereby allowing operations that sleep. + * + * Registered handlers are called in a FIFO manner, i.e. the most recently + * registered handler will be called first. + * + * Return: Handler data &struct amdgpu_dm_irq_handler_data containing the IRQ + * source, handler function, and args + */ +void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev, + struct dc_interrupt_params *int_params, + void (*ih)(void *), + void *handler_args) +{ + struct list_head *hnd_list; + struct amdgpu_dm_irq_handler_data *handler_data; + unsigned long irq_table_flags; + enum dc_irq_source irq_source; + + if (false == validate_irq_registration_params(int_params, ih)) + return DAL_INVALID_IRQ_HANDLER_IDX; + + handler_data = kzalloc(sizeof(*handler_data), GFP_KERNEL); + if (!handler_data) { + DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); + return DAL_INVALID_IRQ_HANDLER_IDX; + } + + init_handler_common_data(handler_data, ih, handler_args, &adev->dm); + + irq_source = int_params->irq_source; + + handler_data->irq_source = irq_source; + + /* Lock the list, add the handler. */ + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + switch (int_params->int_context) { + case INTERRUPT_HIGH_IRQ_CONTEXT: + hnd_list = &adev->dm.irq_handler_list_high_tab[irq_source]; + break; + case INTERRUPT_LOW_IRQ_CONTEXT: + default: + hnd_list = &adev->dm.irq_handler_list_low_tab[irq_source]; + INIT_WORK(&handler_data->work, dm_irq_work_func); + break; + } + + list_add_tail(&handler_data->list, hnd_list); + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + + /* This pointer will be stored by code which requested interrupt + * registration. + * The same pointer will be needed in order to unregister the + * interrupt. */ + + DRM_DEBUG_KMS( + "DM_IRQ: added irq handler: %p for: dal_src=%d, irq context=%d\n", + handler_data, + irq_source, + int_params->int_context); + + return handler_data; +} + +/** + * amdgpu_dm_irq_unregister_interrupt() - Remove a handler from the DM IRQ table + * @adev: The base driver device containing the DM device + * @irq_source: IRQ source to remove the given handler from + * @ih: Function pointer to the interrupt handler to unregister + * + * Go through both low and high context IRQ tables, and find the given handler + * for the given irq source. If found, remove it. Otherwise, do nothing. + */ +void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, + enum dc_irq_source irq_source, + void *ih) +{ + struct list_head *handler_list; + struct dc_interrupt_params int_params; + int i; + + if (false == validate_irq_unregistration_params(irq_source, ih)) + return; + + memset(&int_params, 0, sizeof(int_params)); + + int_params.irq_source = irq_source; + + for (i = 0; i < INTERRUPT_CONTEXT_NUMBER; i++) { + + int_params.int_context = i; + + handler_list = remove_irq_handler(adev, ih, &int_params); + + if (handler_list != NULL) + break; + } + + if (handler_list == NULL) { + /* If we got here, it means we searched all irq contexts + * for this irq source, but the handler was not found. */ + DRM_ERROR( + "DM_IRQ: failed to find irq handler:%p for irq_source:%d!\n", + ih, irq_source); + } +} + +/** + * amdgpu_dm_irq_init() - Initialize DM IRQ management + * @adev: The base driver device containing the DM device + * + * Initialize DM's high and low context IRQ tables. + * + * The N by M table contains N IRQ sources, with M + * &struct amdgpu_dm_irq_handler_data hooked together in a linked list. The + * list_heads are initialized here. When an interrupt n is triggered, all m + * handlers are called in sequence, FIFO according to registration order. + * + * The low context table requires special steps to initialize, since handlers + * will be deferred to a workqueue. See &struct irq_list_head. + */ +int amdgpu_dm_irq_init(struct amdgpu_device *adev) +{ + int src; + struct list_head *lh; + + DRM_DEBUG_KMS("DM_IRQ\n"); + + spin_lock_init(&adev->dm.irq_handler_list_table_lock); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + /* low context handler list init */ + lh = &adev->dm.irq_handler_list_low_tab[src]; + INIT_LIST_HEAD(lh); + /* high context handler init */ + INIT_LIST_HEAD(&adev->dm.irq_handler_list_high_tab[src]); + } + + return 0; +} + +/** + * amdgpu_dm_irq_fini() - Tear down DM IRQ management + * @adev: The base driver device containing the DM device + * + * Flush all work within the low context IRQ table. + */ +void amdgpu_dm_irq_fini(struct amdgpu_device *adev) +{ + int src; + struct list_head *lh; + struct list_head *entry, *tmp; + struct amdgpu_dm_irq_handler_data *handler; + unsigned long irq_table_flags; + + DRM_DEBUG_KMS("DM_IRQ: releasing resources.\n"); + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + /* The handler was removed from the table, + * it means it is safe to flush all the 'work' + * (because no code can schedule a new one). */ + lh = &adev->dm.irq_handler_list_low_tab[src]; + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + + if (!list_empty(lh)) { + list_for_each_safe(entry, tmp, lh) { + handler = list_entry( + entry, + struct amdgpu_dm_irq_handler_data, + list); + flush_work(&handler->work); + } + } + } + /* Deallocate handlers from the table. */ + unregister_all_irq_handlers(adev); +} + +int amdgpu_dm_irq_suspend(struct amdgpu_device *adev) +{ + int src; + struct list_head *hnd_list_h; + struct list_head *hnd_list_l; + unsigned long irq_table_flags; + struct list_head *entry, *tmp; + struct amdgpu_dm_irq_handler_data *handler; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + DRM_DEBUG_KMS("DM_IRQ: suspend\n"); + + /** + * Disable HW interrupt for HPD and HPDRX only since FLIP and VBLANK + * will be disabled from manage_dm_interrupts on disable CRTC. + */ + for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) + dc_interrupt_set(adev->dm.dc, src, false); + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + + if (!list_empty(hnd_list_l)) { + list_for_each_safe (entry, tmp, hnd_list_l) { + handler = list_entry( + entry, + struct amdgpu_dm_irq_handler_data, + list); + flush_work(&handler->work); + } + } + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + return 0; +} + +int amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) +{ + int src; + struct list_head *hnd_list_h, *hnd_list_l; + unsigned long irq_table_flags; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + DRM_DEBUG_KMS("DM_IRQ: early resume\n"); + + /* re-enable short pulse interrupts HW interrupt */ + for (src = DC_IRQ_SOURCE_HPD1RX; src <= DC_IRQ_SOURCE_HPD6RX; src++) { + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) + dc_interrupt_set(adev->dm.dc, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + + return 0; +} + +int amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) +{ + int src; + struct list_head *hnd_list_h, *hnd_list_l; + unsigned long irq_table_flags; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + DRM_DEBUG_KMS("DM_IRQ: resume\n"); + + /** + * Renable HW interrupt for HPD and only since FLIP and VBLANK + * will be enabled from manage_dm_interrupts on enable CRTC. + */ + for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6; src++) { + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) + dc_interrupt_set(adev->dm.dc, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + return 0; +} + +/* + * amdgpu_dm_irq_schedule_work - schedule all work items registered for the + * "irq_source". + */ +static void amdgpu_dm_irq_schedule_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source) +{ + struct list_head *handler_list = &adev->dm.irq_handler_list_low_tab[irq_source]; + struct amdgpu_dm_irq_handler_data *handler_data; + bool work_queued = false; + + if (list_empty(handler_list)) + return; + + list_for_each_entry (handler_data, handler_list, list) { + if (queue_work(system_highpri_wq, &handler_data->work)) { + work_queued = true; + break; + } + } + + if (!work_queued) { + struct amdgpu_dm_irq_handler_data *handler_data_add; + /*get the amdgpu_dm_irq_handler_data of first item pointed by handler_list*/ + handler_data = container_of(handler_list->next, struct amdgpu_dm_irq_handler_data, list); + + /*allocate a new amdgpu_dm_irq_handler_data*/ + handler_data_add = kzalloc(sizeof(*handler_data), GFP_ATOMIC); + if (!handler_data_add) { + DRM_ERROR("DM_IRQ: failed to allocate irq handler!\n"); + return; + } + + /*copy new amdgpu_dm_irq_handler_data members from handler_data*/ + handler_data_add->handler = handler_data->handler; + handler_data_add->handler_arg = handler_data->handler_arg; + handler_data_add->dm = handler_data->dm; + handler_data_add->irq_source = irq_source; + + list_add_tail(&handler_data_add->list, handler_list); + + INIT_WORK(&handler_data_add->work, dm_irq_work_func); + + if (queue_work(system_highpri_wq, &handler_data_add->work)) + DRM_DEBUG("Queued work for handling interrupt from " + "display for IRQ source %d\n", + irq_source); + else + DRM_ERROR("Failed to queue work for handling interrupt " + "from display for IRQ source %d\n", + irq_source); + } +} + +/* + * amdgpu_dm_irq_immediate_work + * Callback high irq work immediately, don't send to work queue + */ +static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + enum dc_irq_source irq_source) +{ + struct amdgpu_dm_irq_handler_data *handler_data; + unsigned long irq_table_flags; + + DM_IRQ_TABLE_LOCK(adev, irq_table_flags); + + list_for_each_entry(handler_data, + &adev->dm.irq_handler_list_high_tab[irq_source], + list) { + /* Call a subcomponent which registered for immediate + * interrupt notification */ + handler_data->handler(handler_data->handler_arg); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +} + +/** + * amdgpu_dm_irq_handler - Generic DM IRQ handler + * @adev: amdgpu base driver device containing the DM device + * @source: Unused + * @entry: Data about the triggered interrupt + * + * Calls all registered high irq work immediately, and schedules work for low + * irq. The DM IRQ table is used to find the corresponding handlers. + */ +static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry) +{ + + enum dc_irq_source src = + dc_interrupt_to_irq_source( + adev->dm.dc, + entry->src_id, + entry->src_data[0]); + + dc_interrupt_ack(adev->dm.dc, src); + + /* Call high irq work immediately */ + amdgpu_dm_irq_immediate_work(adev, src); + /*Schedule low_irq work */ + amdgpu_dm_irq_schedule_work(adev, src); + + return 0; +} + +static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned type) +{ + switch (type) { + case AMDGPU_HPD_1: + return DC_IRQ_SOURCE_HPD1; + case AMDGPU_HPD_2: + return DC_IRQ_SOURCE_HPD2; + case AMDGPU_HPD_3: + return DC_IRQ_SOURCE_HPD3; + case AMDGPU_HPD_4: + return DC_IRQ_SOURCE_HPD4; + case AMDGPU_HPD_5: + return DC_IRQ_SOURCE_HPD5; + case AMDGPU_HPD_6: + return DC_IRQ_SOURCE_HPD6; + default: + return DC_IRQ_SOURCE_INVALID; + } +} + +static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned type, + enum amdgpu_interrupt_state state) +{ + enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dc_interrupt_set(adev->dm.dc, src, st); + return 0; +} + +static inline int dm_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned crtc_id, + enum amdgpu_interrupt_state state, + const enum irq_type dal_irq_type, + const char *func) +{ + bool st; + enum dc_irq_source irq_source; + + struct amdgpu_crtc *acrtc = adev->mode_info.crtcs[crtc_id]; + + if (!acrtc) { + DRM_ERROR( + "%s: crtc is NULL at id :%d\n", + func, + crtc_id); + return 0; + } + + if (acrtc->otg_inst == -1) + return 0; + + irq_source = dal_irq_type + acrtc->otg_inst; + + st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dc_interrupt_set(adev->dm.dc, irq_source, st); + return 0; +} + +static int amdgpu_dm_set_pflip_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned crtc_id, + enum amdgpu_interrupt_state state) +{ + return dm_irq_state( + adev, + source, + crtc_id, + state, + IRQ_TYPE_PFLIP, + __func__); +} + +static int amdgpu_dm_set_crtc_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned crtc_id, + enum amdgpu_interrupt_state state) +{ + return dm_irq_state( + adev, + source, + crtc_id, + state, + IRQ_TYPE_VBLANK, + __func__); +} + +static int amdgpu_dm_set_vline0_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state) +{ + return dm_irq_state( + adev, + source, + crtc_id, + state, + IRQ_TYPE_VLINE0, + __func__); +} + +static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state) +{ + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dc_interrupt_set(adev->dm.dc, irq_source, st); + return 0; +} + +static int amdgpu_dm_set_vupdate_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int crtc_id, + enum amdgpu_interrupt_state state) +{ + return dm_irq_state( + adev, + source, + crtc_id, + state, + IRQ_TYPE_VUPDATE, + __func__); +} + +static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + unsigned int type, + enum amdgpu_interrupt_state state) +{ + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + + dc_interrupt_set(adev->dm.dc, irq_source, st); + return 0; +} + +static const struct amdgpu_irq_src_funcs dm_crtc_irq_funcs = { + .set = amdgpu_dm_set_crtc_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +static const struct amdgpu_irq_src_funcs dm_vline0_irq_funcs = { + .set = amdgpu_dm_set_vline0_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +static const struct amdgpu_irq_src_funcs dm_dmub_outbox_irq_funcs = { + .set = amdgpu_dm_set_dmub_outbox_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +static const struct amdgpu_irq_src_funcs dm_vupdate_irq_funcs = { + .set = amdgpu_dm_set_vupdate_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +static const struct amdgpu_irq_src_funcs dm_dmub_trace_irq_funcs = { + .set = amdgpu_dm_set_dmub_trace_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +static const struct amdgpu_irq_src_funcs dm_pageflip_irq_funcs = { + .set = amdgpu_dm_set_pflip_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +static const struct amdgpu_irq_src_funcs dm_hpd_irq_funcs = { + .set = amdgpu_dm_set_hpd_irq_state, + .process = amdgpu_dm_irq_handler, +}; + +void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) +{ + adev->crtc_irq.num_types = adev->mode_info.num_crtc; + adev->crtc_irq.funcs = &dm_crtc_irq_funcs; + + adev->vline0_irq.num_types = adev->mode_info.num_crtc; + adev->vline0_irq.funcs = &dm_vline0_irq_funcs; + + adev->dmub_outbox_irq.num_types = 1; + adev->dmub_outbox_irq.funcs = &dm_dmub_outbox_irq_funcs; + + adev->vupdate_irq.num_types = adev->mode_info.num_crtc; + adev->vupdate_irq.funcs = &dm_vupdate_irq_funcs; + + adev->dmub_trace_irq.num_types = 1; + adev->dmub_trace_irq.funcs = &dm_dmub_trace_irq_funcs; + + adev->pageflip_irq.num_types = adev->mode_info.num_crtc; + adev->pageflip_irq.funcs = &dm_pageflip_irq_funcs; + + adev->hpd_irq.num_types = adev->mode_info.num_hpd; + adev->hpd_irq.funcs = &dm_hpd_irq_funcs; +} +void amdgpu_dm_outbox_init(struct amdgpu_device *adev) +{ + dc_interrupt_set(adev->dm.dc, + DC_IRQ_SOURCE_DMCUB_OUTBOX, + true); +} + +/** + * amdgpu_dm_hpd_init - hpd setup callback. + * + * @adev: amdgpu_device pointer + * + * Setup the hpd pins used by the card (evergreen+). + * Enable the pin, set the polarity, and enable the hpd interrupts. + */ +void amdgpu_dm_hpd_init(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev_to_drm(adev); + struct drm_connector *connector; + struct drm_connector_list_iter iter; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + + const struct dc_link *dc_link = amdgpu_dm_connector->dc_link; + + if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd) { + dc_interrupt_set(adev->dm.dc, + dc_link->irq_source_hpd, + true); + } + + if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd_rx) { + dc_interrupt_set(adev->dm.dc, + dc_link->irq_source_hpd_rx, + true); + } + } + drm_connector_list_iter_end(&iter); +} + +/** + * amdgpu_dm_hpd_fini - hpd tear down callback. + * + * @adev: amdgpu_device pointer + * + * Tear down the hpd pins used by the card (evergreen+). + * Disable the hpd interrupts. + */ +void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev_to_drm(adev); + struct drm_connector *connector; + struct drm_connector_list_iter iter; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + const struct dc_link *dc_link = amdgpu_dm_connector->dc_link; + + if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd) { + dc_interrupt_set(adev->dm.dc, + dc_link->irq_source_hpd, + false); + } + + if (DC_IRQ_SOURCE_INVALID != dc_link->irq_source_hpd_rx) { + dc_interrupt_set(adev->dm.dc, + dc_link->irq_source_hpd_rx, + false); + } + } + drm_connector_list_iter_end(&iter); +} -- cgit v1.2.3