root/drivers/net/wireless/intel/iwlwifi/pcie/utils.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2025 Intel Corporation
 */

#include <linux/pci.h>
#include <linux/gfp.h>

#include "iwl-io.h"
#include "pcie/utils.h"

void iwl_trans_pcie_dump_regs(struct iwl_trans *trans, struct pci_dev *pdev)
{
#define PCI_DUMP_SIZE           352
#define PCI_MEM_DUMP_SIZE       64
#define PCI_PARENT_DUMP_SIZE    524
#define PREFIX_LEN              32

        static bool pcie_dbg_dumped_once = 0;
        u32 i, pos, alloc_size, *ptr, *buf;
        char *prefix;

        if (pcie_dbg_dumped_once)
                return;

        /* Should be a multiple of 4 */
        BUILD_BUG_ON(PCI_DUMP_SIZE > 4096 || PCI_DUMP_SIZE & 0x3);
        BUILD_BUG_ON(PCI_MEM_DUMP_SIZE > 4096 || PCI_MEM_DUMP_SIZE & 0x3);
        BUILD_BUG_ON(PCI_PARENT_DUMP_SIZE > 4096 || PCI_PARENT_DUMP_SIZE & 0x3);

        /* Alloc a max size buffer */
        alloc_size = PCI_ERR_ROOT_ERR_SRC +  4 + PREFIX_LEN;
        alloc_size = max_t(u32, alloc_size, PCI_DUMP_SIZE + PREFIX_LEN);
        alloc_size = max_t(u32, alloc_size, PCI_MEM_DUMP_SIZE + PREFIX_LEN);
        alloc_size = max_t(u32, alloc_size, PCI_PARENT_DUMP_SIZE + PREFIX_LEN);

        buf = kmalloc(alloc_size, GFP_ATOMIC);
        if (!buf)
                return;
        prefix = (char *)buf + alloc_size - PREFIX_LEN;

        IWL_ERR(trans, "iwlwifi transaction failed, dumping registers\n");

        /* Print wifi device registers */
        sprintf(prefix, "iwlwifi %s: ", pci_name(pdev));
        IWL_ERR(trans, "iwlwifi device config registers:\n");
        for (i = 0, ptr = buf; i < PCI_DUMP_SIZE; i += 4, ptr++)
                if (pci_read_config_dword(pdev, i, ptr))
                        goto err_read;
        print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0);

        IWL_ERR(trans, "iwlwifi device memory mapped registers:\n");
        for (i = 0, ptr = buf; i < PCI_MEM_DUMP_SIZE; i += 4, ptr++)
                *ptr = iwl_read32(trans, i);
        print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0);

        pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR);
        if (pos) {
                IWL_ERR(trans, "iwlwifi device AER capability structure:\n");
                for (i = 0, ptr = buf; i < PCI_ERR_ROOT_COMMAND; i += 4, ptr++)
                        if (pci_read_config_dword(pdev, pos + i, ptr))
                                goto err_read;
                print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET,
                               32, 4, buf, i, 0);
        }

        /* Print parent device registers next */
        if (!pdev->bus->self)
                goto out;

        pdev = pdev->bus->self;
        sprintf(prefix, "iwlwifi %s: ", pci_name(pdev));

        IWL_ERR(trans, "iwlwifi parent port (%s) config registers:\n",
                pci_name(pdev));
        for (i = 0, ptr = buf; i < PCI_PARENT_DUMP_SIZE; i += 4, ptr++)
                if (pci_read_config_dword(pdev, i, ptr))
                        goto err_read;
        print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0);

        /* Print root port AER registers */
        pos = 0;
        pdev = pcie_find_root_port(pdev);
        if (pdev)
                pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR);
        if (pos) {
                IWL_ERR(trans, "iwlwifi root port (%s) AER cap structure:\n",
                        pci_name(pdev));
                sprintf(prefix, "iwlwifi %s: ", pci_name(pdev));
                for (i = 0, ptr = buf; i <= PCI_ERR_ROOT_ERR_SRC; i += 4, ptr++)
                        if (pci_read_config_dword(pdev, pos + i, ptr))
                                goto err_read;
                print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32,
                               4, buf, i, 0);
        }
        goto out;

err_read:
        print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0);
        IWL_ERR(trans, "Read failed at 0x%X\n", i);
out:
        pcie_dbg_dumped_once = 1;
        kfree(buf);
}