Message ID | 20220822185911.170440-3-pierre-louis.bossart@linux.intel.com |
---|---|
State | New |
Headers | show |
Series | ASoC: SOF: Intel: override mclk_id for ES8336 support | expand |
On Mon, 22 Aug 2022 20:59:09 +0200, Pierre-Louis Bossart wrote: > > +#define SSP_BLOB_V1_0_SIZE 84 > +#define SSP_BLOB_V1_0_MDIVC_OFFSET 19 /* offset in u32 */ > +#define SSP_BLOB_V1_5_SIZE 96 > +#define SSP_BLOB_V1_5_MDIVC_OFFSET 21 /* offset in u32 */ This is 84 in bytes, which is equal with SSP_BLOB_V1_0_size. So... > + for (j = 0; j < fmt->fmt_count; j++) { > + u32 *blob; > + int mdivc_offset; > + > + if (cfg->config.size >= SSP_BLOB_V1_0_SIZE) { > + blob = (u32 *)cfg->config.caps; ... the size check is >= 84. If cfg->config.size==84, it may be an out-of-bound read at blob[SSP_BLOB_V1_5_MDIVC_OFFSET]? I don't think this would really matter in practice, but it's better to have a proper check, of course. thanks, Takashi
Hi Amadeusz, >> +int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num) >> +{ >> + struct nhlt_endpoint *epnt; >> + struct nhlt_fmt *fmt; >> + struct nhlt_fmt_cfg *cfg; >> + int mclk_mask = 0; >> + int i, j; >> + >> + if (!nhlt) >> + return 0; >> + >> + epnt = (struct nhlt_endpoint *)nhlt->desc; >> + for (i = 0; i < nhlt->endpoint_count; i++) { >> + >> + /* we only care about endpoints connected to an audio codec >> over SSP */ >> + if (epnt->linktype == NHLT_LINK_SSP && >> + epnt->device_type == NHLT_DEVICE_I2S && >> + epnt->virtual_bus_id == ssp_num) { > > if (epnt->linktype != NHLT_LINK_SSP || > epnt->device_type != NHLT_DEVICE_I2S || > epnt->virtual_bus_id != ssp_num) > continue; > > and then you can remove one indentation level below? Would that work? We still need to move the epnt pointer: epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); and moving this in the endpoint_count loop would be ugly as well. >> + >> + fmt = (struct nhlt_fmt *)(epnt->config.caps + >> epnt->config.size); >> + cfg = fmt->fmt_config; >> + >> + /* >> + * In theory all formats should use the same MCLK but it >> doesn't hurt to >> + * double-check that the configuration is consistent >> + */ >> + for (j = 0; j < fmt->fmt_count; j++) { >> + u32 *blob; >> + int mdivc_offset; >> + >> + if (cfg->config.size >= SSP_BLOB_V1_0_SIZE) { >> + blob = (u32 *)cfg->config.caps; >> + >> + if (blob[1] == SSP_BLOB_VER_2_0) >> + mdivc_offset = SSP_BLOB_V2_0_MDIVC_OFFSET; >> + else if (blob[1] == SSP_BLOB_VER_1_5) >> + mdivc_offset = SSP_BLOB_V1_5_MDIVC_OFFSET; >> + else >> + mdivc_offset = SSP_BLOB_V1_0_MDIVC_OFFSET; >> + >> + mclk_mask |= blob[mdivc_offset] & GENMASK(1, 0); >> + } >> + >> + cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + >> cfg->config.size); >> + } >> + } >> + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); >> + } >> + >> + return mclk_mask; > > Although I understand that it is relegated to the caller, but if both > mclk being set is considered an error maybe add some kind of check here > instead and free callers from having to remember about it? > > if (hweight_long(mclk_mask) != 1) > return -EINVAL; > > return mclk_mask; I went back and forth multiple times on this one. I can't figure out if this would be a bug or a feature, it could be e.g. a test capability and it's supported in hardware. I decided to make the decision in the caller rather than a lower level in the library. If the tools used to generate NHLT don't support this multi-MCLK mode then we could indeed move the test here. > >> +} >> +EXPORT_SYMBOL(intel_nhlt_ssp_mclk_mask); >> + >> static struct nhlt_specific_cfg * >> nhlt_get_specific_cfg(struct device *dev, struct nhlt_fmt *fmt, u8 >> num_ch, >> u32 rate, u8 vbps, u8 bps) >
On 8/23/2022 5:18 PM, Pierre-Louis Bossart wrote: > >>>>> + >>>>> + fmt = (struct nhlt_fmt *)(epnt->config.caps + >>>>> epnt->config.size); >>>>> + cfg = fmt->fmt_config; >>>>> + >>>>> + /* >>>>> + * In theory all formats should use the same MCLK but it >>>>> doesn't hurt to >>>>> + * double-check that the configuration is consistent >>>>> + */ >>>>> + for (j = 0; j < fmt->fmt_count; j++) { >>>>> + u32 *blob; >>>>> + int mdivc_offset; >>>>> + >>>>> + if (cfg->config.size >= SSP_BLOB_V1_0_SIZE) { >>>>> + blob = (u32 *)cfg->config.caps; >>>>> + >>>>> + if (blob[1] == SSP_BLOB_VER_2_0) >>>>> + mdivc_offset = SSP_BLOB_V2_0_MDIVC_OFFSET; >>>>> + else if (blob[1] == SSP_BLOB_VER_1_5) >>>>> + mdivc_offset = SSP_BLOB_V1_5_MDIVC_OFFSET; >>>>> + else >>>>> + mdivc_offset = SSP_BLOB_V1_0_MDIVC_OFFSET; >>>>> + >>>>> + mclk_mask |= blob[mdivc_offset] & GENMASK(1, 0); >> >> One more thing, where does this GENMASK come from, as far as I can tell >> HW specifies and FW uses one bit field to signal that MCLK is enabled? >> (mdivc is simply a value written to HW register to configure it). > > There are two MCLK signals, that's the point of this patch. We need to > find which one is used. Platforms typically use MCLK0 except when they > don't.. > > BIT(0) set in mdivc enables MCLK0 > BIT(1) set in mdivc enabled MCLK1 > > see > https://github.com/thesofproject/sof/blob/44a5200c87625588f0028aa08d560e68f2b8dc82/src/drivers/intel/ssp/mn.c#L150 > >>>>> + } >>>>> + >>>>> + cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + >>>>> cfg->config.size); >>>>> + } >>>>> + } >>>>> + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); >>>>> + } >>>>> + >>>>> + return mclk_mask; >>>> >>>> Although I understand that it is relegated to the caller, but if both >>>> mclk being set is considered an error maybe add some kind of check here >>>> instead and free callers from having to remember about it? >>>> >>>> if (hweight_long(mclk_mask) != 1) >>>> return -EINVAL; >>>> >>>> return mclk_mask; >>> >>> I went back and forth multiple times on this one. I can't figure out if >>> this would be a bug or a feature, it could be e.g. a test capability and >>> it's supported in hardware. I decided to make the decision in the caller >>> rather than a lower level in the library. >>> >>> If the tools used to generate NHLT don't support this multi-MCLK mode >>> then we could indeed move the test here. >>> >> >> Considering comment I added above I've asked Czarek to also check this >> series. I'm not sure it even makes sense to name the field "_mask" when >> it is one bit... > > it's two bits, see above. So I've spend a bit talking with FW team, and you are right, I got confused by one of the tables and some code that specified it as 1 bit field and rest as reserved, while other documents do specify it as a variable range of bits. Going back to return value, the tool I have access to only has support for MCLK0. I guess we can make the assumption for now that everyone connects codec to one clock source and if someone later implements HW where somehow 2 different clocks are used (depending on format) we can refine the check later?
>>>>>> + } >>>>>> + >>>>>> + cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + >>>>>> cfg->config.size); >>>>>> + } >>>>>> + } >>>>>> + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); >>>>>> + } >>>>>> + >>>>>> + return mclk_mask; >>>>> >>>>> Although I understand that it is relegated to the caller, but if both >>>>> mclk being set is considered an error maybe add some kind of check >>>>> here >>>>> instead and free callers from having to remember about it? >>>>> >>>>> if (hweight_long(mclk_mask) != 1) >>>>> return -EINVAL; >>>>> >>>>> return mclk_mask; >>>> >>>> I went back and forth multiple times on this one. I can't figure out if >>>> this would be a bug or a feature, it could be e.g. a test capability >>>> and >>>> it's supported in hardware. I decided to make the decision in the >>>> caller >>>> rather than a lower level in the library. >>>> >>>> If the tools used to generate NHLT don't support this multi-MCLK mode >>>> then we could indeed move the test here. >>>> >>> >>> Considering comment I added above I've asked Czarek to also check this >>> series. I'm not sure it even makes sense to name the field "_mask" when >>> it is one bit... >> >> it's two bits, see above. > > So I've spend a bit talking with FW team, and you are right, I got > confused by one of the tables and some code that specified it as 1 bit > field and rest as reserved, while other documents do specify it as a > variable range of bits. Yeah, I had to ask multiple times as well. It's far from self-explanatory and all the findings were based on NHLT shared with me. See https://github.com/thesofproject/linux/issues/3336#issuecomment-1206176141 for two examples with MCLK0 and MCLK1 used. > Going back to return value, the tool I have access to only has support > for MCLK0. I guess we can make the assumption for now that everyone > connects codec to one clock source and if someone later implements HW > where somehow 2 different clocks are used (depending on format) we can > refine the check later? Indeed it seems that depending on tools versions and targeted silicon, MCLK1 may or may not be supported. I guess it'd be fine to throw a big fat error in case this two-MCLK configuration ever shows-up. That way we'll know for sure what was deployed. Thanks for the feedback, I'll update this shortly.
diff --git a/include/sound/intel-nhlt.h b/include/sound/intel-nhlt.h index 3d5cf201cd802..53470d6a28d65 100644 --- a/include/sound/intel-nhlt.h +++ b/include/sound/intel-nhlt.h @@ -136,6 +136,8 @@ bool intel_nhlt_has_endpoint_type(struct nhlt_acpi_table *nhlt, u8 link_type); int intel_nhlt_ssp_endpoint_mask(struct nhlt_acpi_table *nhlt, u8 device_type); +int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num); + struct nhlt_specific_cfg * intel_nhlt_get_endpoint_blob(struct device *dev, struct nhlt_acpi_table *nhlt, u32 bus_id, u8 link_type, u8 vbps, u8 bps, @@ -169,6 +171,11 @@ static inline int intel_nhlt_ssp_endpoint_mask(struct nhlt_acpi_table *nhlt, u8 return 0; } +static inline int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num) +{ + return 0; +} + static inline struct nhlt_specific_cfg * intel_nhlt_get_endpoint_blob(struct device *dev, struct nhlt_acpi_table *nhlt, u32 bus_id, u8 link_type, u8 vbps, u8 bps, diff --git a/sound/hda/intel-nhlt.c b/sound/hda/intel-nhlt.c index 13bb0ccfb36c0..0323aedb6ecf4 100644 --- a/sound/hda/intel-nhlt.c +++ b/sound/hda/intel-nhlt.c @@ -157,6 +157,67 @@ int intel_nhlt_ssp_endpoint_mask(struct nhlt_acpi_table *nhlt, u8 device_type) } EXPORT_SYMBOL(intel_nhlt_ssp_endpoint_mask); +#define SSP_BLOB_V1_0_SIZE 84 +#define SSP_BLOB_V1_0_MDIVC_OFFSET 19 /* offset in u32 */ +#define SSP_BLOB_V1_5_SIZE 96 +#define SSP_BLOB_V1_5_MDIVC_OFFSET 21 /* offset in u32 */ +#define SSP_BLOB_VER_1_5 0xEE000105 +#define SSP_BLOB_V2_0_MDIVC_OFFSET 20 /* offset in u32 */ +#define SSP_BLOB_VER_2_0 0xEE000200 + +int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num) +{ + struct nhlt_endpoint *epnt; + struct nhlt_fmt *fmt; + struct nhlt_fmt_cfg *cfg; + int mclk_mask = 0; + int i, j; + + if (!nhlt) + return 0; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + for (i = 0; i < nhlt->endpoint_count; i++) { + + /* we only care about endpoints connected to an audio codec over SSP */ + if (epnt->linktype == NHLT_LINK_SSP && + epnt->device_type == NHLT_DEVICE_I2S && + epnt->virtual_bus_id == ssp_num) { + + fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size); + cfg = fmt->fmt_config; + + /* + * In theory all formats should use the same MCLK but it doesn't hurt to + * double-check that the configuration is consistent + */ + for (j = 0; j < fmt->fmt_count; j++) { + u32 *blob; + int mdivc_offset; + + if (cfg->config.size >= SSP_BLOB_V1_0_SIZE) { + blob = (u32 *)cfg->config.caps; + + if (blob[1] == SSP_BLOB_VER_2_0) + mdivc_offset = SSP_BLOB_V2_0_MDIVC_OFFSET; + else if (blob[1] == SSP_BLOB_VER_1_5) + mdivc_offset = SSP_BLOB_V1_5_MDIVC_OFFSET; + else + mdivc_offset = SSP_BLOB_V1_0_MDIVC_OFFSET; + + mclk_mask |= blob[mdivc_offset] & GENMASK(1, 0); + } + + cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size); + } + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return mclk_mask; +} +EXPORT_SYMBOL(intel_nhlt_ssp_mclk_mask); + static struct nhlt_specific_cfg * nhlt_get_specific_cfg(struct device *dev, struct nhlt_fmt *fmt, u8 num_ch, u32 rate, u8 vbps, u8 bps)