/*
 * 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;
}
