Skip to content

QMI_DATA_LEN fields must be u32 on the host since 7.2, or every request is -EINVAL

scope: soc:msm8998 · severity: finding · confidence: proven · subsystem: sensors

The question — why do the light and proximity sensors stop reporting after moving msm8998 from 6.18 to 7.2, when the driver applied unchanged and loads fine?

The answer — the QMI encoder changed underneath it. Upstream fe099c387e06 (“soc: qcom: preserve CPU endianness for QMI_DATA_LEN”, in 7.2) replaced a width-aware read of the length field:

- val8 = *(u8 *)buf_src; /* 6.18: reads ONE byte */
- data_len_value = (u32)val8;
+ memcpy(&data_len_value, buf_src, sizeof(u32)); /* 7.2: reads FOUR */

with an unconditional 4-byte read, on the stated premise that “QMI_DATA_LEN is always of type u32 on the host”. Any driver that declares that field narrower now has the following struct members read as the high bytes of its length. sns_smgr_buffering_req declares

u8 item_len;
struct sns_smgr_buffering_req_item items[SNS_SMGR_DATA_TYPE_COUNT];

so data_len_value picks up three bytes of items[0], comes out far larger than elem_len, and qmi_encode() bails at qmi_encdec.c:392 with -EINVAL before anything reaches the ADSP. The sensor never refuses the request; the request is never sent.

What this rules out — that anything is wrong with the smgr driver’s logic, the sensor firmware, the DT, or the QMI service on the ADSP side. Nothing in the driver’s behaviour changed and no message ever left the host. It also rules out reading the count of affected sensors as a clue: every sensor whose stream is armed through a buffering request fails identically, so which ones a user notices is about which ones the desktop polls, not about which are broken.

The fix, and the trap inside it: the struct field becomes u32, but .elem_size must then be written as the wire size explicitly rather than sizeof_field(...). qmi_encode() derives the on-wire width from elem_size:

data_len_sz = temp_ei->elem_size == sizeof(u8) ? sizeof(u8) : sizeof(u16);

so widening the field while leaving .elem_size = sizeof_field(struct x, len) silently changes the message from a 1-byte to a 2-byte length and breaks it a second, quieter way. In-tree drivers/soc/qcom/qcom_pdr_msg.c is the pattern to copy: u32 domain_list_len; in the struct, .elem_size = sizeof(u8) hardcoded in the ei_array.

How it was established — the 1:1 count match between the encoder error and the driver error, then reading fe099c387e06 against drivers/iio/common/qcom_smgr/qmi/qmi_sns_smgr.{c,h}. Not yet fixed: applying it means auditing every QMI_DATA_LEN site in that driver, not just the buffering request. Related: kernel-7-2-rebase-is-cheap.