Menu
Linux on Microsoft Surface 2

GPU, memory and clocks

From 8 ms per texture to 0.7 ms, memory at 792 MHz and a CPU that lied about its clock.

The tablet ran GNOME, but well below what its hardware allows. The causes were spread out: the graphics driver, the memory tables and the state UEFI leaves at boot.

Textures: 80 % of the time in memcpy

While dragging a window, the profiler showed 80–87 % of GNOME Shell's main thread in memcpy from uncached memory. Drawing one 1045×400 window took about 8 ms.

The cause was row pitch. The GR3D sampler needs a 64-byte pitch for non-power-of-two textures, while the driver allocated them with the 32-byte pitch of a render target. The texture did not fit, so the driver made a packed copy, and it did that on every draw.

From the patch description:

The GR3D sampler has an implicit row stride (16 bytes for power-of-two
textures, 64 bytes for NPOT), but textures were allocated with the 32-byte
render-target pitch. Every NPOT texture whose row was not a multiple of 64
bytes (window textures, icons, glyph atlases, ...) therefore got a "packed"
shadow BO and was re-copied row by row, reading from uncached GEM memory,
on EVERY draw that sampled it.

The fix is simple: allocate such textures with the sampler pitch from the start, so they are read in place. For imported buffers, the copy now refreshes only after the texture is written.

MetricBeforeAfter
Drawing a 1045×400 window7.9 ms0.6–0.7 ms
Dragging a window38–51 fps59 fps
Shell main thread66–92 %33–37 %
Opening the overview0.65–1.1 s0.37–0.57 s

Screen "ghosts" were fixed nearby: the hardware accepts at most 4096 indices per draw, so large draws are now split.

Memory: the real tables were in ACPI

The EMC memory controller ran at 408 MHz against a rated 792 MHz. The clock cannot change without a timing table, which is unique to each board and memory type.

The table was in the tablet itself: \_SB.PEPD.DVF6 in SSDT-6, a tegra11_emc_table of revision 0x42 with 10 frequencies. Its 408 MHz entry matched the live registers, which proved it genuine.

We took the switching algorithm from tegra11_emc.c in NVIDIA's kernel. The surface-emc module never changes voltages: it only checks that VDD_CORE is high enough.

/*
 * Supported targets: 204000 (PLLP/2), 408000, 600000 (PLLC, reserved
 * exclusively), 792000 (PLLM reprogrammed to 792 MHz while EMC runs on PLLP).
 * VDD_CORE (Palmas SMPS457) must already be >= table min_mV; the module
 * never changes voltages.
 */
Test408 MHz792 MHzChange
Latency, ns294207−30 %
Write, 4 threads, MB/s49858192+64 %
memcpy, MB/s17172434+42 %
Read, MB/s16121988+23 %

Validation: memtest at 792 MHz and 1680 frequency switches, 670 of them under load, with zero errors.

CPU: the system lied about its clock

With a new kernel we saw a strange result: on some boots it ran 23 % slower. Configuration and compiler were not to blame. The cycle counter showed a real 990 MHz against a reported 1320.

UEFI hands over with SUPER_CCLKG_DIVIDER = 0x40000203: a hardware clock skipper at 3/4, plus a bit that gives control to the thermal block. T114 has no driver for that block, the kernel gates it as unused, and the throttle freezes for good.

/*
 * Firmware may leave SUPER_CDIV_USE_THERM_CONTROLS set, handing the
 * CPU-G clock skipper to SOC_THERM. There is no Tegra114 soctherm
 * driver and soc_therm gets gated as an unused clock, so a throttle
 * active at that moment stays frozen (e.g. 0x40000203: cclk_g at 3/4
 * while cpufreq reports the full rate). Like NVIDIA's downstream
 * tegra11_super_clk_init(), start with the skipper cleared.
 */
{
    u32 cdiv = readl(clk_base + SUPER_CCLKG_DIVIDER);

    if (cdiv)
        pr_info("tegra114: clearing SUPER_CCLKG_DIVIDER 0x%08x\n",
            cdiv);
    writel(0, clk_base + SUPER_CCLKG_DIVIDER);
}

Clock and thermal control

surface-cpufreq moves the clock from 204 to 1320 MHz together with VDD_CPU:

Clock, MHzVoltage, mV
up to 408900
612940
816990
10201030
12241090
13261120

Protection steps the clock down from 85 °C, drops straight to 612 MHz at 95 °C and shuts down cleanly at 100 °C. A four-core stress test peaked at about 80 °C.

The GPU clock rose from 300 to 492 MHz with no voltage change, cutting a blended 1080p layer from 11.2 to 8.3 ms.

Video

The H.264 decoder runs on the separate AVP processor and appears as a regular /dev/video60 device. Hashes of all frames matched software decoding, and 1080p30 takes about 61 % of one core. FFmpeg, GStreamer, mpv and VLC use it through V4L2.

What we could not achieve

The MSENC encoder starts but rejects every frame with error e2640010, even with the original firmware and replayed commands. This took the most experiments and remains unsolved. GTK 4 on the GPU is impossible by design: it needs GLES3, and GR3D has only GLES2.