349 lines
14 KiB
C
349 lines
14 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only OR AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/*
|
|
* slot2_test.c — Test mapping a second GK210 die at 0x400000000
|
|
*
|
|
* Programs die 2 (Card B, 07:00.0) at the second MMIO slot while
|
|
* bar_swap holds die 0 (Card A, 03:00.0) at 0xC00000000.
|
|
*
|
|
* Both dies are on separate root ports with independent bridge chains,
|
|
* so they shouldn't conflict.
|
|
*
|
|
* Usage:
|
|
* # First: acquire die 0 via bar_swap (from userspace)
|
|
* # Then:
|
|
* insmod slot2_test.ko die_id=2
|
|
* # Check dmesg for results
|
|
* rmmod slot2_test
|
|
*
|
|
* The module programs BARs and bridge windows, does a quick MMIO probe
|
|
* to verify the die responds, then cleans up on rmmod.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("David (Corey) Herrera");
|
|
MODULE_DESCRIPTION("Test second MMIO slot at 0x400000000");
|
|
|
|
#define GK210_VENDOR 0x10de
|
|
#define GK210_DEVICE 0x102d
|
|
#define LOG "slot2_test: "
|
|
|
|
/* Slot 2 addresses (0x400000000) */
|
|
#define SLOT2_BAR1_ADDR 0x400000000ULL /* 16 GB, pref */
|
|
#define SLOT2_BAR0_ADDR 0xF6000000ULL /* 16 MB, non-pref (after bar_swap's F3-F5) */
|
|
#define SLOT2_BAR3_ADDR 0xF7000000ULL /* 32 MB, pref */
|
|
|
|
#define BAR0_SIZE 0x01000000ULL /* 16 MB */
|
|
#define BAR1_SIZE 0x400000000ULL /* 16 GB */
|
|
#define BAR3_SIZE 0x02000000ULL /* 32 MB */
|
|
|
|
/* Bridge windows for slot 2 */
|
|
#define S2_PREF_BASE 0x400000000ULL
|
|
#define S2_PREF_LIMIT 0x7FFFFFFFFULL
|
|
#define S2_MEM_BASE 0xF6000000ULL
|
|
#define S2_MEM_LIMIT 0xF7FFFFFFULL
|
|
|
|
static int die_id = 2;
|
|
module_param(die_id, int, 0444);
|
|
MODULE_PARM_DESC(die_id, "Die to map at slot 2 (default: 2)");
|
|
|
|
#define MAX_CHAIN 4
|
|
|
|
static struct pci_dev *target_dev;
|
|
static struct pci_dev *bridge_chain[MAX_CHAIN];
|
|
static int n_bridges;
|
|
static u32 saved_bars[6];
|
|
static u16 saved_cmd;
|
|
static struct {
|
|
u16 pref_base, pref_limit, mem_base, mem_limit, cmd;
|
|
u32 pref_base_upper, pref_limit_upper;
|
|
} saved_bridge[MAX_CHAIN];
|
|
|
|
/* Sibling die's downstream bridge (need to disable to prevent PLX routing conflict) */
|
|
static struct pci_dev *sibling_downstream;
|
|
static u16 sibling_saved_cmd;
|
|
static u16 sibling_saved_mem_base, sibling_saved_mem_limit;
|
|
static u16 sibling_saved_pref_base, sibling_saved_pref_limit;
|
|
static u32 sibling_saved_pref_base_upper, sibling_saved_pref_limit_upper;
|
|
|
|
static void save_bridge(struct pci_dev *br, int idx)
|
|
{
|
|
pci_read_config_word(br, PCI_PREF_MEMORY_BASE, &saved_bridge[idx].pref_base);
|
|
pci_read_config_word(br, PCI_PREF_MEMORY_LIMIT, &saved_bridge[idx].pref_limit);
|
|
pci_read_config_dword(br, PCI_PREF_BASE_UPPER32, &saved_bridge[idx].pref_base_upper);
|
|
pci_read_config_dword(br, PCI_PREF_LIMIT_UPPER32, &saved_bridge[idx].pref_limit_upper);
|
|
pci_read_config_word(br, PCI_MEMORY_BASE, &saved_bridge[idx].mem_base);
|
|
pci_read_config_word(br, PCI_MEMORY_LIMIT, &saved_bridge[idx].mem_limit);
|
|
pci_read_config_word(br, PCI_COMMAND, &saved_bridge[idx].cmd);
|
|
}
|
|
|
|
static void restore_bridge(struct pci_dev *br, int idx)
|
|
{
|
|
u16 cmd = saved_bridge[idx].cmd;
|
|
pci_write_config_word(br, PCI_COMMAND, cmd & ~(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER));
|
|
pci_write_config_word(br, PCI_PREF_MEMORY_BASE, saved_bridge[idx].pref_base);
|
|
pci_write_config_word(br, PCI_PREF_MEMORY_LIMIT, saved_bridge[idx].pref_limit);
|
|
pci_write_config_dword(br, PCI_PREF_BASE_UPPER32, saved_bridge[idx].pref_base_upper);
|
|
pci_write_config_dword(br, PCI_PREF_LIMIT_UPPER32, saved_bridge[idx].pref_limit_upper);
|
|
pci_write_config_word(br, PCI_MEMORY_BASE, saved_bridge[idx].mem_base);
|
|
pci_write_config_word(br, PCI_MEMORY_LIMIT, saved_bridge[idx].mem_limit);
|
|
pci_write_config_word(br, PCI_COMMAND, cmd);
|
|
}
|
|
|
|
static void program_bridge_windows(struct pci_dev *br)
|
|
{
|
|
u16 cmd;
|
|
|
|
/* Disable decode first */
|
|
pci_read_config_word(br, PCI_COMMAND, &cmd);
|
|
pci_write_config_word(br, PCI_COMMAND, cmd & ~(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_IO));
|
|
|
|
/* Prefetchable window */
|
|
pci_write_config_word(br, PCI_PREF_MEMORY_BASE,
|
|
(u16)((S2_PREF_BASE >> 16) & 0xFFF0) | 0x1); /* 64-bit */
|
|
pci_write_config_word(br, PCI_PREF_MEMORY_LIMIT,
|
|
(u16)((S2_PREF_LIMIT >> 16) & 0xFFF0) | 0x1);
|
|
pci_write_config_dword(br, PCI_PREF_BASE_UPPER32, (u32)(S2_PREF_BASE >> 32));
|
|
pci_write_config_dword(br, PCI_PREF_LIMIT_UPPER32, (u32)(S2_PREF_LIMIT >> 32));
|
|
|
|
/* Non-prefetchable window */
|
|
pci_write_config_word(br, PCI_MEMORY_BASE, (u16)((S2_MEM_BASE >> 16) & 0xFFF0));
|
|
pci_write_config_word(br, PCI_MEMORY_LIMIT, (u16)((S2_MEM_LIMIT >> 16) & 0xFFF0));
|
|
}
|
|
|
|
static void enable_decode(struct pci_dev *dev)
|
|
{
|
|
u16 cmd;
|
|
pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
|
cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_IO;
|
|
pci_write_config_word(dev, PCI_COMMAND, cmd);
|
|
}
|
|
|
|
static void disable_decode(struct pci_dev *dev)
|
|
{
|
|
u16 cmd;
|
|
pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
|
cmd &= ~(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_IO);
|
|
pci_write_config_word(dev, PCI_COMMAND, cmd);
|
|
}
|
|
|
|
static int __init slot2_test_init(void)
|
|
{
|
|
struct pci_dev *dev = NULL;
|
|
struct pci_dev *br;
|
|
void __iomem *bar0_map;
|
|
u32 boot_0, pmc;
|
|
int found = 0, i;
|
|
|
|
pr_info(LOG "=== Slot 2 Test: mapping die %d at 0x%llX ===\n",
|
|
die_id, SLOT2_BAR1_ADDR);
|
|
|
|
/* Find the target die */
|
|
while ((dev = pci_get_device(GK210_VENDOR, GK210_DEVICE, dev)) != NULL) {
|
|
if (found == die_id) {
|
|
target_dev = pci_dev_get(dev);
|
|
break;
|
|
}
|
|
found++;
|
|
}
|
|
|
|
if (!target_dev) {
|
|
pr_err(LOG "Die %d not found (only %d dies)\n", die_id, found);
|
|
return -ENODEV;
|
|
}
|
|
pr_info(LOG "Target: %s\n", pci_name(target_dev));
|
|
|
|
/* Walk bridge chain upward */
|
|
n_bridges = 0;
|
|
br = target_dev->bus->self;
|
|
while (br && n_bridges < MAX_CHAIN) {
|
|
bridge_chain[n_bridges] = pci_dev_get(br);
|
|
save_bridge(br, n_bridges);
|
|
pr_info(LOG "Bridge[%d]: %s\n", n_bridges, pci_name(br));
|
|
n_bridges++;
|
|
if (br->bus && br->bus->self)
|
|
br = br->bus->self;
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* Save die BAR state */
|
|
for (i = 0; i < 6; i++)
|
|
pci_read_config_dword(target_dev, PCI_BASE_ADDRESS_0 + i*4, &saved_bars[i]);
|
|
pci_read_config_word(target_dev, PCI_COMMAND, &saved_cmd);
|
|
|
|
/* Find and disable sibling downstream bridge (PLX routing conflict fix) */
|
|
{
|
|
int sib_id = (die_id % 2 == 0) ? die_id + 1 : die_id - 1;
|
|
struct pci_dev *sib_dev = NULL;
|
|
int sib_found = 0;
|
|
|
|
while ((sib_dev = pci_get_device(GK210_VENDOR, GK210_DEVICE, sib_dev)) != NULL) {
|
|
if (sib_found == sib_id) {
|
|
/* Get sibling's downstream bridge (chain[0]) */
|
|
if (sib_dev->bus && sib_dev->bus->self) {
|
|
sibling_downstream = pci_dev_get(sib_dev->bus->self);
|
|
pr_info(LOG "Sibling downstream bridge: %s\n",
|
|
pci_name(sibling_downstream));
|
|
|
|
/* Save sibling state */
|
|
pci_read_config_word(sibling_downstream, PCI_COMMAND, &sibling_saved_cmd);
|
|
pci_read_config_word(sibling_downstream, PCI_MEMORY_BASE, &sibling_saved_mem_base);
|
|
pci_read_config_word(sibling_downstream, PCI_MEMORY_LIMIT, &sibling_saved_mem_limit);
|
|
pci_read_config_word(sibling_downstream, PCI_PREF_MEMORY_BASE, &sibling_saved_pref_base);
|
|
pci_read_config_word(sibling_downstream, PCI_PREF_MEMORY_LIMIT, &sibling_saved_pref_limit);
|
|
pci_read_config_dword(sibling_downstream, PCI_PREF_BASE_UPPER32, &sibling_saved_pref_base_upper);
|
|
pci_read_config_dword(sibling_downstream, PCI_PREF_LIMIT_UPPER32, &sibling_saved_pref_limit_upper);
|
|
|
|
/* Disable decode and zero windows */
|
|
disable_decode(sibling_downstream);
|
|
pci_write_config_word(sibling_downstream, PCI_MEMORY_BASE, 0xFFFF);
|
|
pci_write_config_word(sibling_downstream, PCI_MEMORY_LIMIT, 0x0000);
|
|
pci_write_config_word(sibling_downstream, PCI_PREF_MEMORY_BASE, 0xFFFF);
|
|
pci_write_config_word(sibling_downstream, PCI_PREF_MEMORY_LIMIT, 0x0000);
|
|
pci_write_config_dword(sibling_downstream, PCI_PREF_BASE_UPPER32, 0);
|
|
pci_write_config_dword(sibling_downstream, PCI_PREF_LIMIT_UPPER32, 0);
|
|
pr_info(LOG "Sibling disabled\n");
|
|
}
|
|
pci_dev_put(sib_dev);
|
|
break;
|
|
}
|
|
sib_found++;
|
|
}
|
|
}
|
|
|
|
/* Disable decode on target chain (top-down) */
|
|
pr_info(LOG "Disabling decode on bridge chain...\n");
|
|
for (i = n_bridges - 1; i >= 0; i--)
|
|
disable_decode(bridge_chain[i]);
|
|
disable_decode(target_dev);
|
|
|
|
/* Program bridge windows (top-down: root port first) */
|
|
pr_info(LOG "Programming bridge windows...\n");
|
|
for (i = n_bridges - 1; i >= 0; i--) {
|
|
program_bridge_windows(bridge_chain[i]);
|
|
pr_info(LOG " %s: pref=0x%llX-0x%llX mem=0x%llX-0x%llX\n",
|
|
pci_name(bridge_chain[i]),
|
|
S2_PREF_BASE, S2_PREF_LIMIT,
|
|
S2_MEM_BASE, S2_MEM_LIMIT);
|
|
}
|
|
|
|
/* Program die BARs */
|
|
pr_info(LOG "Programming die BARs...\n");
|
|
|
|
/* BAR0: 32-bit non-pref at 0xF6000000 */
|
|
pci_write_config_dword(target_dev, PCI_BASE_ADDRESS_0, (u32)SLOT2_BAR0_ADDR);
|
|
target_dev->resource[0].start = SLOT2_BAR0_ADDR;
|
|
target_dev->resource[0].end = SLOT2_BAR0_ADDR + BAR0_SIZE - 1;
|
|
target_dev->resource[0].flags = IORESOURCE_MEM;
|
|
|
|
/* BAR1: 64-bit pref at 0x400000000 (low dword at 0x14, high at 0x18) */
|
|
pci_write_config_dword(target_dev, PCI_BASE_ADDRESS_1,
|
|
(u32)(SLOT2_BAR1_ADDR) | PCI_BASE_ADDRESS_MEM_PREFETCH | PCI_BASE_ADDRESS_MEM_TYPE_64);
|
|
pci_write_config_dword(target_dev, PCI_BASE_ADDRESS_2, (u32)(SLOT2_BAR1_ADDR >> 32));
|
|
target_dev->resource[1].start = SLOT2_BAR1_ADDR;
|
|
target_dev->resource[1].end = SLOT2_BAR1_ADDR + BAR1_SIZE - 1;
|
|
target_dev->resource[1].flags = IORESOURCE_MEM | IORESOURCE_PREFETCH | IORESOURCE_MEM_64;
|
|
|
|
/* BAR3: 64-bit pref at 0xF7000000 (low dword at 0x1C, high at 0x20) */
|
|
pci_write_config_dword(target_dev, PCI_BASE_ADDRESS_3,
|
|
(u32)(SLOT2_BAR3_ADDR) | PCI_BASE_ADDRESS_MEM_PREFETCH | PCI_BASE_ADDRESS_MEM_TYPE_64);
|
|
pci_write_config_dword(target_dev, PCI_BASE_ADDRESS_4, (u32)(SLOT2_BAR3_ADDR >> 32));
|
|
target_dev->resource[3].start = SLOT2_BAR3_ADDR;
|
|
target_dev->resource[3].end = SLOT2_BAR3_ADDR + BAR3_SIZE - 1;
|
|
target_dev->resource[3].flags = IORESOURCE_MEM | IORESOURCE_PREFETCH | IORESOURCE_MEM_64;
|
|
|
|
pr_info(LOG " BAR0=0x%llX BAR1=0x%llX BAR3=0x%llX\n",
|
|
SLOT2_BAR0_ADDR, SLOT2_BAR1_ADDR, SLOT2_BAR3_ADDR);
|
|
|
|
/* Enable decode (top-down) */
|
|
pr_info(LOG "Enabling decode...\n");
|
|
for (i = n_bridges - 1; i >= 0; i--)
|
|
enable_decode(bridge_chain[i]);
|
|
enable_decode(target_dev);
|
|
|
|
/* Set power state to D0 */
|
|
pci_set_power_state(target_dev, PCI_D0);
|
|
pci_save_state(target_dev);
|
|
|
|
/* Verify: try to read BAR0 registers */
|
|
pr_info(LOG "Probing BAR0 at 0x%llX...\n", SLOT2_BAR0_ADDR);
|
|
bar0_map = ioremap(SLOT2_BAR0_ADDR, BAR0_SIZE);
|
|
if (!bar0_map) {
|
|
pr_err(LOG "ioremap BAR0 FAILED\n");
|
|
goto done;
|
|
}
|
|
|
|
boot_0 = ioread32(bar0_map + 0x0); /* NV_PMC_BOOT_0 — GPU ID register */
|
|
pmc = ioread32(bar0_map + 0x200); /* NV_PMC_ENABLE */
|
|
|
|
pr_info(LOG " BOOT_0 (0x000): 0x%08X\n", boot_0);
|
|
pr_info(LOG " PMC_EN (0x200): 0x%08X\n", pmc);
|
|
|
|
if (boot_0 == 0xFFFFFFFF) {
|
|
pr_err(LOG " >>> BAR0 READS ALL-F — die not responding at 0x400! <<<\n");
|
|
} else if (boot_0 == 0x00000000) {
|
|
pr_warn(LOG " >>> BAR0 reads zero — die may need SBR first <<<\n");
|
|
} else {
|
|
pr_info(LOG " >>> DIE RESPONDS AT 0x400000000! <<<\n");
|
|
pr_info(LOG " GPU ID: GK%X (revision 0x%X)\n",
|
|
(boot_0 >> 20) & 0x1FF, boot_0 & 0xFF);
|
|
}
|
|
|
|
iounmap(bar0_map);
|
|
|
|
done:
|
|
pr_info(LOG "Module loaded. rmmod to clean up.\n");
|
|
return 0;
|
|
}
|
|
|
|
static void __exit slot2_test_exit(void)
|
|
{
|
|
int i;
|
|
|
|
pr_info(LOG "Cleaning up...\n");
|
|
|
|
/* Disable decode */
|
|
if (target_dev)
|
|
disable_decode(target_dev);
|
|
for (i = 0; i < n_bridges; i++)
|
|
disable_decode(bridge_chain[i]);
|
|
|
|
/* Restore die BARs */
|
|
if (target_dev) {
|
|
for (i = 0; i < 6; i++)
|
|
pci_write_config_dword(target_dev, PCI_BASE_ADDRESS_0 + i*4, saved_bars[i]);
|
|
pci_write_config_word(target_dev, PCI_COMMAND, saved_cmd);
|
|
pci_dev_put(target_dev);
|
|
}
|
|
|
|
/* Restore bridge chain */
|
|
for (i = 0; i < n_bridges; i++) {
|
|
restore_bridge(bridge_chain[i], i);
|
|
pci_dev_put(bridge_chain[i]);
|
|
}
|
|
|
|
/* Restore sibling */
|
|
if (sibling_downstream) {
|
|
pci_write_config_word(sibling_downstream, PCI_MEMORY_BASE, sibling_saved_mem_base);
|
|
pci_write_config_word(sibling_downstream, PCI_MEMORY_LIMIT, sibling_saved_mem_limit);
|
|
pci_write_config_word(sibling_downstream, PCI_PREF_MEMORY_BASE, sibling_saved_pref_base);
|
|
pci_write_config_word(sibling_downstream, PCI_PREF_MEMORY_LIMIT, sibling_saved_pref_limit);
|
|
pci_write_config_dword(sibling_downstream, PCI_PREF_BASE_UPPER32, sibling_saved_pref_base_upper);
|
|
pci_write_config_dword(sibling_downstream, PCI_PREF_LIMIT_UPPER32, sibling_saved_pref_limit_upper);
|
|
pci_write_config_word(sibling_downstream, PCI_COMMAND, sibling_saved_cmd);
|
|
pci_dev_put(sibling_downstream);
|
|
}
|
|
|
|
pr_info(LOG "Cleaned up. Die %d released.\n", die_id);
|
|
}
|
|
|
|
module_init(slot2_test_init);
|
|
module_exit(slot2_test_exit);
|