Subject: [RFC] endianness issues in drivers/net/ethernet/qlogic/qed

From: Al Viro <[email-masked]>
Date: Mon, 18 Sep 2017 18:49:40 +0100

"qed: Utilize FW 8.10.3.0" has attempted some endianness
annotations in that driver; unfortunately, either annotations are
BS or the driver is genuinely broken on big-endian hosts.

	For example, struct init_qm_vport_params is claimed to have
->vport_wfq little-endian 16bit.  However, *all* uses are host-endian -
the things like
                vport_params[i].vport_wfq = (wfq_speed * QED_WFQ_UNIT) /
                                                min_pf_rate;
and it's not even "... and at some point we convert it to little-endian
in place, or when copying to another instance".  It is consistently
host-endian.  If that's how it should be, that __le16 is BS; otherwise,
the driver's broken on big-endian.

	Another example:
struct qm_rf_pq_map {
        __le32 reg;
#define QM_RF_PQ_MAP_PQ_VALID_MASK              0x1
#define QM_RF_PQ_MAP_PQ_VALID_SHIFT             0
#define QM_RF_PQ_MAP_RL_ID_MASK                 0xFF
#define QM_RF_PQ_MAP_RL_ID_SHIFT                1
#define QM_RF_PQ_MAP_VP_PQ_ID_MASK              0x1FF
#define QM_RF_PQ_MAP_VP_PQ_ID_SHIFT             9
#define QM_RF_PQ_MAP_VOQ_MASK                   0x1F
#define QM_RF_PQ_MAP_VOQ_SHIFT                  18
#define QM_RF_PQ_MAP_WRR_WEIGHT_GROUP_MASK      0x3
#define QM_RF_PQ_MAP_WRR_WEIGHT_GROUP_SHIFT     23
#define QM_RF_PQ_MAP_RL_VALID_MASK              0x1
#define QM_RF_PQ_MAP_RL_VALID_SHIFT             25
#define QM_RF_PQ_MAP_RESERVED_MASK              0x3F
#define QM_RF_PQ_MAP_RESERVED_SHIFT             26
};
with instances of that manipulated with
                memset(&tx_pq_map, 0, sizeof(tx_pq_map));
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_PQ_VALID, 1);
                SET_FIELD(tx_pq_map.reg,
                          QM_RF_PQ_MAP_RL_VALID, rl_valid ? 1 : 0);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_VP_PQ_ID, first_tx_pq_id);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_RL_ID,
                          rl_valid ?
                          p_params->pq_params[i].vport_id : 0);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_VOQ, voq);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_WRR_WEIGHT_GROUP,
                          p_params->pq_params[i].wrr_group);
                /* Write PQ map entry to CAM */
                STORE_RT_REG(p_hwfn, QM_REG_TXPQMAP_RT_OFFSET + pq_id,
                             *((u32 *)&tx_pq_map));
SET_FIELD manipulates the damn thing as *host-endian* - e.g.
                SET_FIELD(v, QM_RF_PQ_MAP_RL_ID, n)
would expand to v = (v & ~0x1fe) | ((n & 0xff) << 1).  Then we proceed to
pass tx_pq_map.reg (fetched in a bloody convoluted way) to
qed_init_store_rt_reg(), which stores it into p_hwfn->rt_data.init_val[...],
which is apparently host-endian.

So what is this __le32 about?

The really worrying case is this:

/* Binary buffer header */
struct bin_buffer_hdr {
        __le32 offset;
        __le32 length;
};

int qed_init_fw_data(struct qed_dev *cdev, const u8 *data)
{
        struct qed_fw_data *fw = cdev->fw_data;
        struct bin_buffer_hdr *buf_hdr;
        u32 offset, len;

        if (!data) {
                DP_NOTICE(cdev, "Invalid fw data\n");
                return -EINVAL;
        }

        /* First Dword contains metadata and should be skipped */
        buf_hdr = (struct bin_buffer_hdr *)data;

        offset = buf_hdr[BIN_BUF_INIT_FW_VER_INFO].offset;
        fw->fw_ver_info = (struct fw_ver_info *)(data + offset);

        offset = buf_hdr[BIN_BUF_INIT_CMD].offset;
        fw->init_ops = (union init_op *)(data + offset);

We are clearly using those as host-endian here.  Yet in _that_
case fixed-endian would appear to make sense, unless we want
separate firmware images for e.g. amd64 and sparc64 hosts...

Another fun one is struct regpair:
        p_ramrod->qp_handle_for_cqe.hi = cpu_to_le32(qp->qp_handle.hi);
        p_ramrod->qp_handle_for_cqe.lo = cpu_to_le32(qp->qp_handle.lo);
Both sides are struct regpair and by the look of helper macros it *is*
meant to be little-endian.  Here, however, one of those (and not
necessary p_ramrod->qp_handle_for_cqe) is host-endian.

Is that driver intended to be used on big-endian hosts at all?