DF-4833 / tfd_len_check.c
/* * DF-4833 unit-level reproduction of the TFD TB0 length-encoding truncation * in iwm_send_cmd() -- sys/dev/netif/iwm/if_iwm_util.c:282-283 * * Reproduces, offline, the exact expression: * desc->tbs[0].hi_n_len = htole16(iwm_get_dma_hi_addr(paddr) * | ((hdrlen + paylen) << 4)); * against the exact constants from sys/dev/netif/iwm/if_iwmreg.h: * struct iwm_cmd_header { 4 bytes } (if_iwmreg.h:6859-6864) * struct iwm_cmd_header_wide { 8 bytes } (if_iwmreg.h:6866-6874) * IWM_MAX_CMD_PAYLOAD_SIZE ((4096 - 4) - 4) = 4088 (if_iwmreg.h:6881) * struct iwm_tfd_tb.hi_n_len : uint16_t, bits 0-3 dma hi, bits 4-15 len * (if_iwmreg.h:1852-1858) * * The TFD spec (comment at if_iwmreg.h:1882-1883) caps one transmit buffer * at (4K - 4) = 4092 bytes. iwm_send_cmd() admits paylen up to 4088 with a * wide (8-byte) header, i.e. hdrlen+paylen == 4096, which both exceeds the * hw per-buffer cap and truncates the 12-bit length field to 0. */ #include <stdio.h> #include <stdint.h> struct iwm_cmd_header { uint8_t code, flags, idx, qid; } __attribute__((packed)); struct iwm_cmd_header_wide { uint8_t opcode, group_id, idx, qid; uint16_t length; uint8_t reserved, version; } __attribute__((packed)); #define IWM_MAX_CMD_PAYLOAD_SIZE ((4096 - 4) - (int)sizeof(struct iwm_cmd_header)) #define TB_MAX (4096 - 4) /* hw cap: if_iwmreg.h:1882-1883 */ static uint16_t encode_tb0_len(unsigned long paddr, int hdrlen, int paylen) { /* iwm_get_dma_hi_addr(): if_iwmreg.h:1841-1844 -- 4 bits */ uint8_t hi = (uint8_t)(((paddr >> 16) >> 16) & 0xF); /* the expression under audit, if_iwm_util.c:282-283 */ uint16_t hi_n_len = (uint16_t)(hi | ((hdrlen + paylen) << 4)); return hi_n_len; /* length field = bits 4..15 */ } int main(void) { int hdrlen = (int)sizeof(struct iwm_cmd_header_wide); /* wide hdr = 8 */ unsigned long paddr = 0x123456789ab0; /* any plausible DMA addr */ printf("IWM_MAX_CMD_PAYLOAD_SIZE = %d, hdr_wide = %d, hw TB cap = %d\n", IWM_MAX_CMD_PAYLOAD_SIZE, hdrlen, TB_MAX); printf("%8s %10s %14s %14s %s\n", "paylen", "totlen", "enc.len(bits)", "dec.length", "verdict"); int cases[] = { 4084, 4085, 4086, 4087, 4088, IWM_MAX_CMD_PAYLOAD_SIZE }; for (unsigned i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) { int paylen = cases[i]; int totlen = hdrlen + paylen; uint16_t h = encode_tb0_len(paddr, hdrlen, paylen); unsigned declen = (h >> 4) & 0xFFF; printf("%8d %10d %14u %14u %s\n", paylen, totlen, ((unsigned)(totlen) << 4) & 0xFFFF, declen, totlen > TB_MAX ? "EXCEEDS HW TB CAP (4K-4)" : (declen == 0 ? "TRUNCATED TO ZERO LENGTH" : "ok")); } printf("\nAt paylen=%d (the maximum iwm_send_cmd() accepts) with a wide " "header,\nthe firmware-visible TB0 length is 0 -- the command " "payload is invisible to fw.\n", IWM_MAX_CMD_PAYLOAD_SIZE); return 0; } |