JRP Innovations
Posted on
• Programming

Part 2 – NVIDIA GTX 1650 & Turing GPU Investigation: Finding the Missing TPC

Author

Well, that escalated quickly.

In Part 1 of this investigation, I went down a rabbit hole after reading about some absolutely fascinating work being done to unlock NVIDIA GPUs through voltage fault injection, eFuse manipulation, and early-boot firmware modifications.

To reiterate the question I'm broadening the existing research to:

Can we identify and potentially recover disabled hardware on an older NVIDIA GPUs, purely to increase consumer performance? Specifically my disposable PNY GTX 1650?

I spent some time building a dedicated Arch Linux research environment, reading hardware registers, poking around Nouveau's driver initialization process, and generally bullying a depressed graphics card.

And guess what?

I QUICKLY FOUND THE DISABLED TPC.

Not only did we identify the hardware configuration responsible for the missing processing units, we independently verified that configuration using Nouveau's own graphics-engine initialization routines.

Now, before anyone gets too excited, no, I haven't actually unlocked anything yet, or done much other than reconnaissance. But we've taken this from some very overly enthusiastic napkin hypothesizing to actual measurements of hardware and software interop.

And that's a pretty damn satisfying start for me.


The sacrificial lamb, revisited

For anyone who missed Part 1, here's what we're working with:

Component Specification
Graphics card PNY GeForce GTX 1650
GPU die TU117-300-A1
PCI Device ID 10de:1f82
Subsystem ID 196e:1343
VRAM 4 GB GDDR5
Memory ICs 4× SK Hynix H5GQ8H24MJR-R4C
SPI flash ISSI IS25WP080
Stock CUDA cores 896
Full TU117 CUDA cores 1,024

A fully enabled TU117 contains 16 streaming multiprocessors (SMs), organized into eight Texture Processing Clusters (TPCs), with two SMs per TPC.

The desktop GTX 1650 exposes only 14 SMs across seven TPCs; that's a missing TPC, or 128 CUDA cores, representing a theoretical 14.3% increase in shader resources if the hardware can be recovered and was not disabled due to performance binning.

That's not necessarily a 14.3% benchmarked performance increase. I'm going to take the moment to remind myself that even if it can be unlocked, any performance benefit will still be use-case dependant.

Nonetheless, there's a very real architectural difference between the product NVIDIA sold and the maximum configuration supported by a TU117 die. My research is understanding how that difference is enforced on this gen of consumer cards.


Building a GPU interrogation room

First order of business was setting up a fresh Arch Linux installation specifically for this project. It gave me a much needed opportunity to clean my desk, and get an old i7-7000 16GB DDR4 Dell PC out of it's case and into a proper test-bench setup.

I used GRUB and prepped boot entries for three configurations:

  • Normal: An untouched recovery environment.
  • RAW: Prevent Nouveau or NVIDIA's other graphics drivers from automatically binding to the card.
  • Nouveau: Allow the open-source driver to initialize the GPU for comparative measurements.

The RAW environment uses the following kernel parameters:

modprobe.blacklist=nouveau,nova_core,nova_drm log_buf_len=8M

The system also has Intel HD Graphics 630 integrated graphics, which is rather convenient. I can run the operating system and access it over SSH without depending on the NVIDIA card I'm experimenting with.

This becomes particularly important once we start doing things that NVIDIA might not appreciate. Though, for those of you who are used to NVIDIA's proprietary bull**** this introduces the additional caveat that now the card is headless and hates being initialized under normal circumstances.

The GTX 1650 enumerated normally at PCI address 0000:01:00.0, with a 16 MiB BAR0 register region.

However, the initial PCI configuration showed:

Control: I/O- Mem- BusMaster-

Region 0: Memory at ee000000
          [disabled] [size=16M]

The GPU was present, but PCI memory decoding wasn't enabled.

Linux provides a conveniently straightforward way to correct that for an unbound PCI device:

BDF=0000:01:00.0
DEV="/sys/bus/pci/devices/$BDF"

echo 1 > "$DEV/enable"

After enabling it:

PCI COMMAND: 0x0103

Control: I/O+ Mem+ BusMaster-

We now had access to the GPU's memory-mapped hardware registers without loading a graphics driver.

I wrote a small Python utility using mmap() to read selected 32-bit registers from /sys/bus/pci/devices/0000:01:00.0/resource0.

The first target was NVIDIA's PMC_BOOT_0, at BAR0 offset 0x000000.

PMC_BOOT_0: 0x167000A1

Success!

The 0x167 chipset identifier corresponds to TU117, meaning we'd successfully interrogated the GPU's own hardware identification rather than merely reading its PCI device ID.

I officially obtained direct MMIO communication with the chip. Easy step 1.


Finding the missing TPC

This is where existing community research became tremendously useful.

On November 5, 2026, a researcher using the handle 333cra published a TU104 floorsweep investigation involving an RTX 2060 KO.

They identified a set of Turing fuse-status registers in the 0x021xxx region and demonstrated that their values corresponded with the card's active configuration and graphics topology. These registers describe part of NVIDIA's floorsweeping configuration.

For the unimitated like I was 2 days ago, floorsweeping is the process of disabling specific portions of a GPU die. This might occur because those portions failed manufacturing tests, for other product configuration reasons, or my favorite: because NVIDIA needed to manufacture a lower-tier product for economic reasons.

Rather than manufacturing entirely different silicon for every SKU, manufacturers can use one physical silicon print (a die), with different functional units enabled or disabled by a mix of software and hardware controls. The relevant question here, is whether the disabled hardware is actually broken, or simply configured not to operate.

Using the known TU104 addresses as a starting point, I wrote another small Python tool to read the speculated registers on my TU117.

And the results were initially promising!

=== TU117 RAW FUSE PROBE ===

PMC_BOOT_0          = 0x167000a1
PMC_ENABLE          = 0x40000101

FUSE_GPC_STATUS     = 0x0000003c

FUSE_TPC_GPC0       = 0x00000032
FUSE_TPC_GPC1       = 0x00000030

CTRL_TPC_GPC0       = 0x00000000
CTRL_TPC_GPC1       = 0x00000000

FEATURE_OVERRIDE    = 0x00000000
PRIV_SECURITY       = 0x00000001

OH. THAT'S INTERESTING.

TU117 physically implements two Graphics Processing Clusters (GPCs), each containing up to four TPCs.

The Turing TPC fuse-status registers use six-bit masks. For TU117, bits 4 and 5 correspond to TPC positions that don't physically exist, so those bits being set is expected.

That means we need to examine the lower four bits.

Let's decode the actual results:

Unit Fuse value Lower 4 bits Interpretation
GPC 0 0x32 0010 TPC 1 disabled
GPC 1 0x30 0000 No TPCs disabled

And there it is.

GPC 0, TPC 1 is the missing processing cluster. The other seven TPCs appear enabled.

The GPC-level fuse status, 0x3C, also makes sense: its lowest two bits are clear, indicating that both physically implemented GPCs are enabled.

My guess at the configured topology is therefore:

TU117-300-A1

GPC 0
  TPC 0  ENABLED
  TPC 1  DISABLED  <-- OUR TARGET
  TPC 2  ENABLED
  TPC 3  ENABLED

GPC 1
  TPC 0  ENABLED
  TPC 1  ENABLED
  TPC 2  ENABLED
  TPC 3  ENABLED

Total: 7 of 8 TPCs enabled

Seven TPCs, two SMs per TPC, 64 CUDA cores per SM. That's 896 CUDA cores and that is exactly what NVIDIA advertises for this card.

Now, that alone wasn't enough for me. The registers were originally documented on a different Turing die, and matching the advertised configuration is strong circumstantial evidence, not an independent confirmation of the graphics engine's actual initialized state.

This is an experiment and prototyping before breaking things is cheaper than breaking them first. So, naturally, I put the shovel away and got my excavator out.


Asking the graphics engine to investigate itself

Nouveau exposes another set of registers describing the GPU's initialized graphics topology.

I attempted to read:

0x409604  GR_TOPOLOGY
0x502608  GPC0_TPC_COUNT
0x50A608  GPC1_TPC_COUNT

The results were curious:

GR_TOPOLOGY      = 0x04020002

GPC0_TPC_COUNT   = 0xBADF1200
GPC1_TPC_COUNT   = 0xBADF1200

The global topology register reported two GPCs, which was encouraging. The per-GPC registers, on the other hand, returned BADF1200.

After some research it seems NVIDIA uses BADF values for certain invalid or inaccessible register transactions. In this context, the results were consistent with trying to access graphics-engine register blocks that weren't yet enabled or were not allowing direct reads.

This was all, RAW with no video drivers. So while the fuse block was accessible, the graphics engine wasn't necessarily ready to answer our questions.

That gave me an obvious next step: Let's just let Nouveau initialize the card, and ask it what it sees.

Nouveau, GSP, and why initialization matters

I rebooted into my legacy Nouveau boot entry that uses the below kernel launch flag.

nouveau.config=NvGspRm=0

This was to disable the GSP-RM path and force Nouveau's legacy initialization routine, which was useful because much more of the graphics-engine setup is visible in the open-source driver's code.

The relevant logs showed:

NVIDIA TU117 (167000a1)
bios: version 90.17.2a.00.78
pmu: firmware unavailable
fb: 4096 MiB GDDR5
Initialized nouveau 1.4.2

Unfortunately, after fighting it for a bit just having the driver loaded wasn't enough to get my desired topology confirmation. This left me back to the drawing board briefly. I wanted to examine the data structures that Nouveau populates when it initializes the graphics engine, rather than recompiling to a custom kernel immediately, so I decided to use eBPF tracing.


Questioning Nouveau with bpftrace

The Nouveau driver has a function called:

gf100_gr_oneinit()

Despite the name, this initialization infrastructure is used across multiple NVIDIA GPU generations, including the Turing code path we're interested in. The function reads hardware topology information and populates a struct gf100_gr containing information such as:

u8 gpc_nr;
u8 tpc_nr[32];
u8 tpc_max;
u8 tpc_total;

This is exactly the information I was excavating for.

Fortunately, my Arch kernel provided BTF type information for the Nouveau module, and the relevant function symbols were available for kernel probing. I used bpftrace to attach probes to the graphics-engine constructor, one-time initialization function, and regular initialization function.

The first attempt was less exciting than I'd hoped. I could see the constructor execute:

[GR] tu102_gr_new called

But the actual graphics initialization callbacks weren't firing (Maybe it's cause this card is running headless, maybe it's Maybelline). I unbound and rebound the GPU to Nouveau multiple times, and the result was the same, the device was being constructed but that alone didn't cause Nouveau to initialize the graphics processing units.

This is when I personally remembered how stupid NVIDIA GPUs can be headless without a monitor, I hadn't actually asked the GPU to do anything graphical. So I tried creating a graphics context using Mesa's surfaceless EGL implementation.

EGL_PLATFORM=surfaceless \
DRI_PRIME=pci-0000_01_00_0 \
LIBGL_DEBUG=verbose \
eglinfo -B

The output was a delightful mixture of success and failure; Mesa correctly identified:

OpenGL renderer:
zink Vulkan 1.4
(NVIDIA GeForce GTX 1650 (NVK TU117))

But it also produced:

MESA: error: ZINK: vkQueueWaitIdle failed
       (VK_ERROR_DEVICE_LOST)

MESA: error: ZINK: vkQueueSubmit failed
       (VK_ERROR_DEVICE_LOST)

Well... Better than nothing. 😂 The graphics workload wasn't healthy, and I'm certainly didn't seem to get successful headless rendering or compute execution.But something much more important happened in the other SSH session! My kernel probes started firing!

So the while the whole thing was complaining, it was still at least beginning it's init functions:

=== GR ONEINIT RESULT ===

Return: 0

GPCs: 2

GPC0 TPCs: 3
GPC1 TPCs: 4

TPC total: 7
TPC maximum: 4
SM mappings: 7

THERE IT WAS. Nouveau's graphics-engine initialization confirmed precisely what the fuse registers predicted and the subsequent gf100_gr_init_() callbacks repeatedly reported the same topology. This was the confirmation I wanted. I no longer felt I was guessing what a particular register meant anymore. I could at least independently observe the effective hardware configuration of the die through two different mechanisms:

  1. Direct MMIO readings of the GPU's floorsweep fuse-status registers.
  2. Nouveau's own initialized graphics-engine topology data.

Best part is when both agree!

GPC 0 has three active TPCs. GPC 1 has four. The fuse mask specifically indicates physical GPC 0 / TPC 1 as the disabled unit based on other researchers findings. One small note that almost tripped me up: Nouveau's sm_nr = 7 in this particular initialization structure reflects its internal mapping entries; it doesn't mean the card has only seven physical streaming multiprocessors. The GTX 1650's seven active TPCs seem to corollary to the 14 NVIDIA SMs.

At this point, I'm mostly considering the topology investigation successful, the missing TPC has been located.

Now comes the much more dangerous question. Can we make NVIDIA's hardware configuration change its mind?


Serving a subpoena to the fuse override controller

Back into RAW mode we went.

This time, the goal was to investigate the hardware responsible for controlling or reporting fuse overrides.

Kate's earlier research into NVIDIA's GA100 demonstrated that the relationship between physical fuses and effective hardware configuration can be considerably more complicated than a simple permanent on/off bit. Across projects, some hardware supports software-controlled disabling of additional processing units. Other architectures contain more elaborate redundancy mechanisms. I am not going to naively assume that a vulnerability found on GA100 exists on TU117.

BUT!, actually just that for now, I reserve that right here, I haven't gone mask off maximally destructive bad cop on this thing yet. NVIDIA's hardware register layouts seem to change between architectures from other research, and what appears to be a corresponding register address may not actually have the same function.

Using experimental addresses proposed in the TU104 floorsweep discussion, I extended my Python MMIO utility to perform read-only measurements of suspected fuse-controller registers.

Here's the resulting survey:

BAR0 offset Candidate register Value
0x020040 EN_SW_OVERRIDE? 0x00000000
0x020084 DIS_SW_OVERRIDE? 0x00FF84CF
0x020350 OPT_GPC_DISABLE? 0xBADF5040
0x0205C4 OPT_GPC_DEFECTIVE? 0xBADF5108
0x021C1C STATUS_OPT_GPC 0x0000003C
0x021C38 STATUS_TPC_GPC0 0x00000032
0x021C3C STATUS_TPC_GPC1 0x00000030
0x021838 CTRL_TPC_GPC0 0x00000000
0x02183C CTRL_TPC_GPC1 0x00000000
0x0213F0 FUSE_OVERRIDE_DISABLE 0x00000000
0x021434 OPT_PRIV_SEC_EN 0x00000001
0x021218 OPT_SEC_DEBUG_EN 0x00000001

Well, this made me sad, our known fuse-status registers continue to return exactly their expected values. But the potential controller addresses are considerably less cooperative.

Two of them return BADF error patterns, one returns zero, and another returns a value that doesn't immediately resemble an ordinary enable/disable mask. We saw this and noted it earlier, BADF responses are consistent with inaccessible or invalid hardware register transactions. This doesn't really give me enough to establish whether the suspected registers from other people's research actually exist, are protected, or are simply mapped incorrectly.

Similarly, reading zero from a proposed software-override register doesn't show that it exists, that it is writable, or that it can remove an already-enforced physical fuse restriction. There's still some more work to do here.

Looking for defective versus disabled hardware

There's another distinction that matters tremendously for this experiment, disabled processing units are not necessarily a defective processing unit.

If NVIDIA disabled a TPC because it genuinely failed manufacturing tests, recovering it might accomplish little beyond making the graphics engine unstable. But if that TPC was disabled for product segmentation, there is at least a plausible reason to investigate whether it might function normally. This is just one of those weird economic parts of computing that has happened forever, across all manufacturers.

Though interestingly some NVIDIA architectures expose separate indications for disabled and defective units. The obvious next step was to see whether similar registers could be identified on TU117.

Using additional candidate addresses extrapolated from later NVIDIA architectures, I expanded the survey again, and the results were:

OPT_TPC_DISABLE_GPC0?
0x0203A0 = 0xFFFFFFFF

OPT_TPC_DEFECT_GPC0?
0x0203A4 = 0xFFFFFFFF

OPT_TPC_DISABLE_GPC1?
0x0203A8 = 0xFFFFFFFF

OPT_TPC_DEFECT_GPC1?
0x0203AC = 0xBADF5040

Not what I wantedddddddd. Those are not usable defect indicators? An all-ones & BADF value responses from hardware registers could indicate an unmapped or unanswered transaction, depending on the device and access path. None of this seemed like something I could interpret as an ordinary defect mask from other similar research that did find proper defect masks.

What I have concluded is that the straightforward address-translation approach seems insufficient to identify these controller registers on TU117.

That doesn't establish that the registers don't exist, or that the hardware is impossible to unlock, I just still need a better map of the actual Turing fuse-controller implementation before making further assumptions about those values. To be frank it's not an incredibly unsurprising outcome.

I am reverse engineering a GPU, they put millions of R&D into locking down, I'm not filling out an NVIDIA-approved configuration form and signing an NDA with them lol.


What can I conclude from part 2?

Let's clarify where I'm at for now:

Question Finding
Does TU117 physically support eight TPCs in its full design? Yes
Does this GTX 1650 expose seven active TPCs? Yes
Do the fuse-status registers identify the disabled location? Yes, GPC 0 / TPC 1
Does Nouveau independently confirm the 3+4 configuration? Yes
Is the disabled TPC functional? Unknown
Was it disabled for defects or product segmentation? Unknown
Can software override its disable state? Unknown
Can TU117's permanent fuse configuration be manipulated? Unknown
Have we enabled any additional CUDA cores? No

And that is where the investigation stands until later this weekend, we know the full TU117 architecture has eight possible TPCs. We know my particular GTX 1650 operates with seven. We know exactly which physical TPC position is disabled according to its floorsweep status, and we've verified the resulting enabled topology through Nouveau.

The distinction matters, lnowing where a door is doesn't mean we have the key, or even that there's a functioning room behind it. But at least I'm not randomly wandering around the building anymore and now we know where to start looking for the keys and some of the places they may be hidden.

What comes next?

I haven't desoldered the IS25WP080 VBIOS flash yet, and no permanent changes have been made to the GPU.

Every hardware-register experiment documented here has been read-only. That is about to become a much less reliable description of this project.

The next phase will involve investigating the actual fuse-controller architecture, studying which restrictions are enforced by physical fuses versus software-controlled configuration, and determining whether there are any reversible interventions worth attempting. If those avenues fail, there's always the rather more entertaining world of firmware modification, external SPI flash emulation, and potentially electrical fault injection.

And unlike a production GPU I actually depend on, the best part is this GTX 1650 is entirely disposable. I'm perfectly happy to sacrifice it in the name of understanding something useful. I'd much rather document why an unlock fails than accidentally destroy the card without learning anything.

Still, there is something wonderfully entertaining about spending this much effort investigating an entry-level graphics card from 2019. We're talking about a theoretical 128 additional CUDA cores on a GPU that was never powerful to begin with. For me, the point is understanding how manufacturers configure their hardware, how those decisions persist through boot and firmware initialization, and whether a device's capabilities are genuinely determined by the silicon or merely by the configuration it's allowed to expose.

And if documenting these findings eventually saves someone else a few weekends of research, or helps another person investigate a different NVIDIA GPU, that's a win in my book.

So, Part 2 ends with a definite answer to our first question! We found the disabled TPC.

Part 3 will get to ask the much more destructive dangerous one, what happens if we poke it with a stick? 💀


References and related research