Why does the EC repeat PDO[0] during GET_PDOS?

Which OS (Operating System)? Linux - not Linux related if I’am correct (that’s why i’am here… not sure)
Which release of your OS? Debian 13 6.12.90+deb13.1-amd64
Which Framework product: Laptop 16, AMD 7040 Series, AMD Radeon™ RX 7700S.

So I write a tool for investigating the UCSI problem. I find in some layer is getting off the PDO report for the OS. I have tested the 180W FW charger with original cable, on BIOS 4.04.
The EC reporting 4 PDO correctly for the OS’s GET_PDOS command (0x10), but after the others is failed to report.
I find something is happening in the EC what not supposed to be… BUT I can’t confirm that I am looking at the correct EC code (i don’t get along on the github repos).
So about the happening:

  • first 4 PDOs are report correctly.
    -second round of PDOs: The PDO[4] is repeating the PDO[0] and afterward empty responses.
    At this point the EC and the OS is in a “panic”, sometime the next command answer is a hard denies from the EC (that is the “Failed to GET_CABLE_PROPERTY”) and the OS (or EC?), caps the EPR into non existent for the charger because it is failed to report the table (here we go to the <100W power cap: 20V 5A).

Where is the 4.04 BIOS EC part what is handling this command from the OS: 0x10 → GET_PDOS?

So based on this and some help of the AI: The interpretation of the PDO list is out of bound and reporting false on the second round after this the OS drop the table because it is out of standard (duplicated entry)?

Please help, I am on the right way or completely wrong?

For future investigation here is the code what I used for this, and my output after that with the factory charger:

CODE:

from bcc import BPF
import ctypes

# --- 1. KERNEL eBPF C CODE ---
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

struct event_t {
    u64 command;        // Full 64-bit UCSI command field
    int ret;            // Return value / read bytes status
    u32 raw_data[8];    // 32-byte data buffer for payloads
    u32 size;           // Actual requested size in bytes
    u8 is_return;       // 0: Entry request, 1: Return response
};

struct map_val_t {
    void *data;
    size_t size;
    u64 command;
};

BPF_HASH(buf_map, u64, struct map_val_t);
BPF_PERF_OUTPUT(events);

// Hook for ucsi_run_command (6 arguments)
int trace_run_command_entry(struct pt_regs *ctx, void *ucsi, u64 command, u32 *cci, void *data, size_t size) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    
    struct map_val_t val = {};
    val.data = data;
    val.size = size;
    val.command = command;
    buf_map.update(&pid_tgid, &val);
    
    struct event_t event = {};
    event.command = command;
    event.size = size;
    event.ret = 0;
    event.is_return = 0;
    events.perf_submit(ctx, &event, sizeof(event));
    
    return 0;
}

// Hook for ucsi_send_command (4 arguments)
int trace_send_command_entry(struct pt_regs *ctx, void *ucsi, u64 command, void *data, size_t size) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    
    struct map_val_t val = {};
    val.data = data;
    val.size = size;
    val.command = command;
    buf_map.update(&pid_tgid, &val);
    
    struct event_t event = {};
    event.command = command;
    event.size = size;
    event.ret = 0;
    event.is_return = 0;
    events.perf_submit(ctx, &event, sizeof(event));
    
    return 0;
}

// Unified return hook using tracked values from the hash map
int trace_return_command(struct pt_regs *ctx) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    struct map_val_t *val = buf_map.lookup(&pid_tgid);

    if (val) {
        struct event_t event = {};
        event.command = val->command;
        event.size = val->size;
        event.ret = PT_REGS_RC(ctx);
        event.is_return = 1;
        
        if (val->data && val->size > 0) {
            size_t read_size = val->size > 32 ? 32 : val->size;
            bpf_probe_read(&event.raw_data, read_size, val->data);
        }
        
        events.perf_submit(ctx, &event, sizeof(event));
        buf_map.delete(&pid_tgid);
    }
    return 0;
}
"""

# --- 2. UCSI PROTOCOL DICTIONARY & PARSERS ---
UCSI_COMMANDS = {
    0x01: "PPM_RESET", 0x02: "CANCEL", 0x03: "CONNECTOR_RESET", 0x04: "ACK_CC_CI",
    0x05: "SET_NOTIFICATION_ENABLE", 0x06: "GET_CAPABILITY", 0x07: "GET_CONNECTOR_CAPABILITY",
    0x08: "SET_UOM", 0x09: "SET_UOR", 0x0A: "SET_PDM", 0x0B: "SET_PDR",
    0x0C: "GET_ALTERNATE_MODES", 0x0D: "GET_CAM_SUPPORTED", 0x0E: "GET_CURRENT_CAM",
    0x0F: "SET_NEW_CAM", 0x10: "GET_PDOS", 0x11: "GET_CABLE_PROPERTY",
    0x12: "GET_CONNECTOR_STATUS", 0x13: "GET_ERROR_STATUS"
}

def decode_pdo(pdo, index):
    if pdo == 0: return
    pdo_type = (pdo >> 30) & 0x3
    print(f"    PDO[{index}]: 0x{pdo:08X} ", end="")
    if pdo_type == 0b00:
        voltage_mv = ((pdo >> 10) & 0x3FF) * 50
        current_ma = (pdo & 0x3FF) * 10
        epr_capable = (pdo >> 23) & 0x1
        print(f"-> Fixed Supply: {voltage_mv / 1000.0:.2f}V @ {current_ma / 1000.0:.2f}A (EPR Capable: {'Yes' if epr_capable else 'No'})")
    elif pdo_type == 0b01:
        max_voltage = ((pdo >> 20) & 0x3FF) * 50
        min_voltage = ((pdo >> 10) & 0x3FF) * 50
        max_power = (pdo & 0x3FF) * 250
        print(f"-> Battery: {min_voltage / 1000.0:.2f}V - {max_voltage / 1000.0:.2f}V, Max {max_power / 1000.0:.2f}W")
    elif pdo_type == 0b10:
        max_voltage = ((pdo >> 20) & 0x3FF) * 50
        min_voltage = ((pdo >> 10) & 0x3FF) * 50
        max_current = (pdo & 0x3FF) * 10
        print(f"-> Variable Supply: {min_voltage / 1000.0:.2f}V - {max_voltage / 1000.0:.2f}V, Max {max_current / 1000.0:.2f}A")
    elif pdo_type == 0b11:
        apdo_type = (pdo >> 28) & 0x3
        if apdo_type == 0b00:
            max_voltage = ((pdo >> 17) & 0xFF) * 100
            min_voltage = ((pdo >> 8) & 0xFF) * 100
            max_current = (pdo & 0x7F) * 50
            print(f"-> SPR PPS: {min_voltage / 1000.0:.2f}V - {max_voltage / 1000.0:.2f}V, Max {max_current / 1000.0:.2f}A")
        elif apdo_type == 0b01:
            max_voltage = ((pdo >> 17) & 0x7FF) * 100
            min_voltage = ((pdo >> 8) & 0x1FF) * 100
            pdp = pdo & 0xFF
            print(f"-> EPR AVS: {min_voltage / 1000.0:.2f}V - {max_voltage / 1000.0:.2f}V, {pdp}W")

def decode_generic_response(cmd, raw_bytes):
    if not raw_bytes or len(raw_bytes) == 0: return
    if cmd == 0x12:
        status_64 = int.from_bytes(raw_bytes[:8], 'little')
        op_modes = {0: "No Partner", 1: "USB Default", 2: "BC 1.2", 3: "Type-C 1.5A", 4: "Type-C 3.0A", 5: "USB PD", 6: "Unknown"}
        op_mode = status_64 & 0x7
        connected = (status_64 >> 3) & 0x1
        pwr_dir = "Provider" if ((status_64 >> 4) & 0x1) else "Consumer"
        partner_type = {0: "DFP (Host)", 1: "UFP (Device)", 2: "Powered Cable (No UFP)", 3: "Powered Cable (With UFP)", 4: "Dual-Role"}.get((status_64 >> 8) & 0x7, "Unknown")
        print(f"    [DECODED STATUS]: Connection: {'Connected' if connected else 'Disconnected'} | Mode: {op_modes.get(op_mode, 'Unknown')} | Direction: {pwr_dir} | Partner: {partner_type}")

# --- 3. RUNTIME INITIALIZATION ---
b = BPF(text=bpf_text)

# Assign differentiated hooks based on strict signature mapping
try:
    b.attach_kprobe(event="ucsi_run_command", fn_name="trace_run_command_entry")
    b.attach_kretprobe(event="ucsi_run_command", fn_name="trace_return_command")
except Exception as e: print(f"[-] Could not hook ucsi_run_command: {e}")

for target in ["ucsi_send_command", "ucsi_send_command_common"]:
    try:
        b.attach_kprobe(event=target, fn_name="trace_send_command_entry")
        b.attach_kretprobe(event=target, fn_name="trace_return_command")
    except Exception as e: pass

print("[*] UCSI Fixed Core Sniffer Active. Monitoring live transmissions...")

def print_event(cpu, data, size):
    event = b["events"].event(data)
    cmd_code = event.command & 0xFF
    conn_num = (event.command >> 16) & 0x7F
    cmd_name = UCSI_COMMANDS.get(cmd_code, f"UNKNOWN_CMD_0x{cmd_code:02X}")
    device_str = f"USBC000:{conn_num:02d}"
    
    if event.is_return == 0:
        print(f"\n[>] KERNEL INITIATED COMMAND: {cmd_name} {device_str}")
        if cmd_code == 0x10:
            partner = "Device Profiles" if ((event.command >> 23) & 0x1) else "Host Profiles"
            offset = (event.command >> 24) & 0xFF
            print(f"    [Command Metadata]: Source: {partner} | Offset Index: {offset} | Expected Size: {event.size} Bytes")
    else:
        print(f"[<] RESPONSE RECEIVED. Status (ret): {event.ret}")
        if event.ret < 0: return

        raw_bytes = bytearray()
        for val in event.raw_data:
            raw_bytes.extend(val.to_bytes(4, 'little'))
            
        safe_size = event.size if event.size <= 32 else 32
        raw_bytes = raw_bytes[:safe_size]

        print(f"    Raw Response Hex: 0x{raw_bytes.hex().upper()}")

        if cmd_code == 0x10:
            has_data = False
            for i in range(8):
                if event.raw_data[i] != 0:
                    has_data = True
                    decode_pdo(event.raw_data[i], i)
            if not has_data:
                print("    (Data buffer empty or populated with nulls.)")
        else:
            decode_generic_response(cmd_code, raw_bytes)

b["events"].open_perf_buffer(print_event)
try:
    while True: b.perf_buffer_poll()
except KeyboardInterrupt: print("\n[*] Exiting...")

Results:

[*] UCSI Fixed Core Sniffer Active. Monitoring live transmissions...

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x024800000000000000
    [DECODED STATUS]: Connection: Disconnected | Mode: BC 1.2 | Direction: Consumer | Partner: DFP (Host)

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x00482C200000000001
    [DECODED STATUS]: Connection: Disconnected | Mode: No Partner | Direction: Consumer | Partner: DFP (Host)

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x420A2B40C209C74201
    [DECODED STATUS]: Connection: Disconnected | Mode: BC 1.2 | Direction: Consumer | Partner: Powered Cable (No UFP)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x2C91910A2CD112002CB11400C2411600
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)
    PDO[1]: 0x0012D12C -> Fixed Supply: 9.00V @ 3.00A (EPR Capable: No)
    PDO[2]: 0x0014B12C -> Fixed Supply: 15.00V @ 3.00A (EPR Capable: No)
    PDO[3]: 0x001641C2 -> Fixed Supply: 20.00V @ 4.50A (EPR Capable: No)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes
[<] RESPONSE RECEIVED. Status (ret): 4
    Raw Response Hex: 0x2C91910A0000000000000000
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x40022B40F4D1C79201
    [DECODED STATUS]: Connection: Disconnected | Mode: No Partner | Direction: Consumer | Partner: Powered Cable (No UFP)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x2C91910A2CD112002CB11400F4411600
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)
    PDO[1]: 0x0012D12C -> Fixed Supply: 9.00V @ 3.00A (EPR Capable: No)
    PDO[2]: 0x0014B12C -> Fixed Supply: 15.00V @ 3.00A (EPR Capable: No)
    PDO[3]: 0x001641F4 -> Fixed Supply: 20.00V @ 5.00A (EPR Capable: No)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes
[<] RESPONSE RECEIVED. Status (ret): 4
    Raw Response Hex: 0x2C91910A0000000000000000
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)

[>] KERNEL INITIATED COMMAND: GET_CABLE_PROPERTY USBC000:04

[>] KERNEL INITIATED COMMAND: GET_CABLE_PROPERTY USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 5
    Raw Response Hex: 0x2078641403

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:02

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:02
[<] RESPONSE RECEIVED. Status (ret): -5

[>] KERNEL INITIATED COMMAND: GET_ERROR_STATUS USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x0001

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04

[>] KERNEL INITIATED COMMAND: GET_CONNECTOR_STATUS USBC000:04
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x00002B40F4D1C79201
    [DECODED STATUS]: Connection: Disconnected | Mode: No Partner | Direction: Consumer | Partner: DFP (Host)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x2C91910A2CD112002CB11400F4411600
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)
    PDO[1]: 0x0012D12C -> Fixed Supply: 9.00V @ 3.00A (EPR Capable: No)
    PDO[2]: 0x0014B12C -> Fixed Supply: 15.00V @ 3.00A (EPR Capable: No)
    PDO[3]: 0x001641F4 -> Fixed Supply: 20.00V @ 5.00A (EPR Capable: No)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes
[<] RESPONSE RECEIVED. Status (ret): 4
    Raw Response Hex: 0x2C91910A0000000000000000
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes
[<] RESPONSE RECEIVED. Status (ret): 0
    Raw Response Hex: 0x00000000000000000000000000000000
    (Data buffer empty or populated with nulls.)

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:01

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:01
[<] RESPONSE RECEIVED. Status (ret): 0
    Raw Response Hex: 0x000000000000000000000000

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 0 | Expected Size: 16 Bytes
[<] RESPONSE RECEIVED. Status (ret): 16
    Raw Response Hex: 0x2C91910A2CD112002CB11400F4411600
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)
    PDO[1]: 0x0012D12C -> Fixed Supply: 9.00V @ 3.00A (EPR Capable: No)
    PDO[2]: 0x0014B12C -> Fixed Supply: 15.00V @ 3.00A (EPR Capable: No)
    PDO[3]: 0x001641F4 -> Fixed Supply: 20.00V @ 5.00A (EPR Capable: No)

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes

[>] KERNEL INITIATED COMMAND: GET_PDOS USBC000:04
    [Command Metadata]: Source: Device Profiles | Offset Index: 4 | Expected Size: 12 Bytes
[<] RESPONSE RECEIVED. Status (ret): 4
    Raw Response Hex: 0x2C91910A0000000000000000
    PDO[0]: 0x0A91912C -> Fixed Supply: 5.00V @ 3.00A (EPR Capable: Yes)

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:01

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:01
[<] RESPONSE RECEIVED. Status (ret): 0
    Raw Response Hex: 0x000000000000000000000000

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:01

[>] KERNEL INITIATED COMMAND: GET_ALTERNATE_MODES USBC000:01
[<] RESPONSE RECEIVED. Status (ret): 0
    Raw Response Hex: 0x000000000000000000000000
^C
[*] Exiting...

Hi,

I have already tracked this one down.
The OS sends the ucsi command to the EC. The EC forwards that to the USB PD firmware. The usb firmware has bugs, so it fails to return useful data.
So, not a OS or EC problem.
We, end-users, don’t have the source code for the usb-pd firmware, so cannot fix it.
The usb firmware is pretty fragile. E.g.
“sudo lsusb -v” crashes the firmware.
It also crashes based on certain patterns of data being passed to attached devices.

Some more details here: