text: #include <stdint.h>
  #include <stddef.h>
  #include <stdlib.h>
  #include <string.h>
  
  #include <cpuinfo.h>
  #include <arm/linux/api.h>
  #if defined(__ANDROID__)
  	#include <arm/android/api.h>
  #endif
  #include <arm/api.h>
  #include <arm/midr.h>
  #include <linux/api.h>
  #include <cpuinfo/internal-api.h>
  #include <cpuinfo/log.h>
  #include <cpuinfo/common.h>
  
  
  #define CLUSTERS_MAX 3
  
  static inline bool bitmask_all(uint32_t bitfield, uint32_t mask) {
  	return (bitfield & mask) == mask;
  }
  
  /* Description of core clusters configuration in a chipset (identified by series and model number) */
  struct cluster_config {
  	/* Number of cores (logical processors) */
  	uint8_t cores;
  	/* ARM chipset series (see cpuinfo_arm_chipset_series enum) */
  	uint8_t series;
  	/* Chipset model number (see cpuinfo_arm_chipset struct) */
  	uint16_t model;
  	/* Number of heterogenous clusters in the CPU package */
  	uint8_t clusters;
  	/*
  	 * Number of cores in each cluster:
  	 # - Symmetric configurations: [0] = # cores
  	 * - big.LITTLE configurations: [0] = # LITTLE cores, [1] = # big cores
  	 * - Max.Med.Min configurations: [0] = # Min cores, [1] = # Med cores, [2] = # Max cores
  	 */
  	uint8_t cluster_cores[CLUSTERS_MAX];
  	/*
  	 * MIDR of cores in each cluster:
  	 * - Symmetric configurations: [0] = core MIDR
  	 * - big.LITTLE configurations: [0] = LITTLE core MIDR, [1] = big core MIDR
  	 * - Max.Med.Min configurations: [0] = Min core MIDR, [1] = Med core MIDR, [2] = Max core MIDR
  	 */
  	uint32_t cluster_midr[CLUSTERS_MAX];
  };
  
  /*
   * The list of chipsets where MIDR may not be unambigiously decoded at least on some devices.
   * The typical reasons for impossibility to decoded MIDRs are buggy kernels, which either do not report all MIDR
   * information (e.g. on ATM7029 kernel doesn't report CPU Part), or chipsets have more than one type of cores
   * (i.e. 4x Cortex-A53 + 4x Cortex-A53 is out) and buggy kernels report MIDR information only about some cores
   * in /proc/cpuinfo (either only online cores, or only the core that reads /proc/cpuinfo). On these kernels/chipsets,
   * it is not possible to detect all core types by just parsing /proc/cpuinfo, so we use chipset name and this table to
   * find their MIDR (and thus microarchitecture, cache, etc).
   *
   * Note: not all chipsets with heterogeneous multiprocessing need an entry in this table. The following HMP
   * chipsets always list information about all cores in /proc/cpuinfo:
   *
   * - Snapdragon 660
   * - Snapdragon 820 (MSM8996)
   * - Snapdragon 821 (MSM8996PRO)
   * - Snapdragon 835 (MSM8998)
   * - Exynos 8895
   * - Kirin 960
   *
   * As these are all new processors, there is hope that this table won't uncontrollably grow over time.
   */
  static const struct cluster_config cluster_configs[] = {
  #if CPUINFO_ARCH_ARM
  	{
  		/*
  		 * MSM8916 (Snapdragon 410): 4x Cortex-A53
  		 * Some AArch32 phones use non-standard /proc/cpuinfo format.
  		 */
  		.cores = 4,
  		.series = cpuinfo_arm_chipset_series_qualcomm_msm,
  		.model = UINT16_C(8916),
  		.clusters = 1,
  		.cluster_cores = {
  			[0] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD030),
  		},
  	},
  	{
  		/*
  		 * MSM8939 (Snapdragon 615): 4x Cortex-A53 + 4x Cortex-A53
  		 * Some AArch32 phones use non-standard /proc/cpuinfo format.
  		 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_qualcomm_msm,
  		.model = UINT16_C(8939),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD034),
  		},
  	},
  #endif
  	{
  		/* MSM8956 (Snapdragon 650): 2x Cortex-A72 + 4x Cortex-A53 */
  		.cores = 6,
  		.series = cpuinfo_arm_chipset_series_qualcomm_msm,
  		.model = UINT16_C(8956),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD080),
  		},
  	},
  	{
  		/* MSM8976/MSM8976PRO (Snapdragon 652/653): 4x Cortex-A72 + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_qualcomm_msm,
  		.model = UINT16_C(8976),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD080),
  		},
  	},
  	{
  		/* MSM8992 (Snapdragon 808): 2x Cortex-A57 + 4x Cortex-A53 */
  		.cores = 6,
  		.series = cpuinfo_arm_chipset_series_qualcomm_msm,
  		.model = UINT16_C(8992),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD033),
  			[1] = UINT32_C(0x411FD072),
  		},
  	},
  	{
  		/* MSM8994/MSM8994V (Snapdragon 810): 4x Cortex-A57 + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_qualcomm_msm,
  		.model = UINT16_C(8994),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD032),
  			[1] = UINT32_C(0x411FD071),
  		},
  	},
  #if CPUINFO_ARCH_ARM
  	{
  		/* Exynos 5422: 4x Cortex-A15 + 4x Cortex-A7 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_samsung_exynos,
  		.model = UINT16_C(5422),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC073),
  			[1] = UINT32_C(0x412FC0F3),
  		},
  	},
  	{
  		/* Exynos 5430: 4x Cortex-A15 + 4x Cortex-A7 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_samsung_exynos,
  		.model = UINT16_C(5430),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC074),
  			[1] = UINT32_C(0x413FC0F3),
  		},
  	},
  #endif /* CPUINFO_ARCH_ARM */
  	{
  		/* Exynos 5433: 4x Cortex-A57 + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_samsung_exynos,
  		.model = UINT16_C(5433),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD031),
  			[1] = UINT32_C(0x411FD070),
  		},
  	},
  	{
  		/* Exynos 7420: 4x Cortex-A57 + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_samsung_exynos,
  		.model = UINT16_C(7420),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4, 
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD032),
  			[1] = UINT32_C(0x411FD070),
  		},
  	},
  	{
  		/* Exynos 8890: 4x Mongoose + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_samsung_exynos,
  		.model = UINT16_C(8890),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x531F0011),
  		},
  	},
  #if CPUINFO_ARCH_ARM
  	{
  		/* Kirin 920: 4x Cortex-A15 + 4x Cortex-A7 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_hisilicon_kirin,
  		.model = UINT16_C(920),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC075),
  			[1] = UINT32_C(0x413FC0F3),
  		},
  	},
  	{
  		/* Kirin 925: 4x Cortex-A15 + 4x Cortex-A7 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_hisilicon_kirin,
  		.model = UINT16_C(925),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC075),
  			[1] = UINT32_C(0x413FC0F3),
  		},
  	},
  	{
  		/* Kirin 928: 4x Cortex-A15 + 4x Cortex-A7 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_hisilicon_kirin,
  		.model = UINT16_C(928),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC075),
  			[1] = UINT32_C(0x413FC0F3),
  		},
  	},
  #endif /* CPUINFO_ARCH_ARM */
  	{
  		/* Kirin 950: 4x Cortex-A72 + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_hisilicon_kirin,
  		.model = UINT16_C(950),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD080),
  		},
  	},
  	{
  		/* Kirin 955: 4x Cortex-A72 + 4x Cortex-A53 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_hisilicon_kirin,
  		.model = UINT16_C(955),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD080),
  		},
  	},
  #if CPUINFO_ARCH_ARM
  	{
  		/* MediaTek MT8135: 2x Cortex-A7 + 2x Cortex-A15 */
  		.cores = 4,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(8135),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 2,
  			[1] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC073),
  			[1] = UINT32_C(0x413FC0F2),
  		},
  	},
  #endif
  	{
  		/* MediaTek MT8173: 2x Cortex-A72 + 2x Cortex-A53 */
  		.cores = 4,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(8173),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 2,
  			[1] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD032),
  			[1] = UINT32_C(0x410FD080),
  		},
  	},
  	{
  		/* MediaTek MT8176: 2x Cortex-A72 + 4x Cortex-A53 */
  		.cores = 6,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(8176),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD032),
  			[1] = UINT32_C(0x410FD080),
  		},
  	},
  #if CPUINFO_ARCH_ARM64
  	{
  		/*
  		 * MediaTek MT8735: 4x Cortex-A53
  		 * Some AArch64 phones use non-standard /proc/cpuinfo format.
  		 */
  		.cores = 4,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(8735),
  		.clusters = 1,
  		.cluster_cores = {
  			[0] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  		},
  	},
  #endif
  #if CPUINFO_ARCH_ARM
  	{
  		/*
  		 * MediaTek MT6592: 4x Cortex-A7 + 4x Cortex-A7
  		 * Some phones use non-standard /proc/cpuinfo format.
  		 */
  		.cores = 4,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(6592),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC074),
  			[1] = UINT32_C(0x410FC074),
  		},
  	},
  	{
  		/* MediaTek MT6595: 4x Cortex-A17 + 4x Cortex-A7 */
  		.cores = 8,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(6595),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC075),
  			[1] = UINT32_C(0x410FC0E0),
  		},
  	},
  #endif
  	{
  		/* MediaTek MT6797: 2x Cortex-A72 + 4x Cortex-A53 + 4x Cortex-A53 */
  		.cores = 10,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(6797),
  		.clusters = 3,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  			[2] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD034),
  			[2] = UINT32_C(0x410FD081),
  		},
  	},
  	{
  		/* MediaTek MT6799: 2x Cortex-A73 + 4x Cortex-A53 + 4x Cortex-A35 */
  		.cores = 10,
  		.series = cpuinfo_arm_chipset_series_mediatek_mt,
  		.model = UINT16_C(6799),
  		.clusters = 3,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 4,
  			[2] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD041),
  			[1] = UINT32_C(0x410FD034),
  			[2] = UINT32_C(0x410FD092),
  		},
  	},
  	{
  		/* Rockchip RK3399: 2x Cortex-A72 + 4x Cortex-A53 */
  		.cores = 6,
  		.series = cpuinfo_arm_chipset_series_rockchip_rk,
  		.model = UINT16_C(3399),
  		.clusters = 2,
  		.cluster_cores = {
  			[0] = 4,
  			[1] = 2,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FD034),
  			[1] = UINT32_C(0x410FD082),
  		},
  	},
  #if CPUINFO_ARCH_ARM
  	{
  		/* Actions ATM8029: 4x Cortex-A5
  		 * Most devices use non-standard /proc/cpuinfo format.
  		 */
  		.cores = 4,
  		.series = cpuinfo_arm_chipset_series_actions_atm,
  		.model = UINT16_C(7029),
  		.clusters = 1,
  		.cluster_cores = {
  			[0] = 4,
  		},
  		.cluster_midr = {
  			[0] = UINT32_C(0x410FC051),
  		},
  	},
  #endif
  };
  
  /*
   * Searches chipset name in mapping of chipset name to cores' MIDR values. If match is successful, initializes MIDR
   * for all clusters' leaders with tabulated values.
   *
   * @param[in] chipset - chipset (SoC) name information.
   * @param clusters_count - number of CPU core clusters detected in the SoC.
   * @param cluster_leaders - indices of core clusters' leaders in the @p processors array.
   * @param processors_count - number of usable logical processors in the system.
   * @param[in,out] processors - array of logical processor descriptions with pre-parsed MIDR, maximum frequency,
   *                             and decoded core cluster (package_leader_id) information.
   *                             Upon successful return, processors[i].midr for all clusters' leaders contains the
   *                             tabulated MIDR values.
   * @param verify_midr - indicated whether the function should check that the MIDR values to be assigned to leaders of
   *                      core clusters are consistent with known parts of their parsed values.
   *                      Set if to false if the only MIDR value parsed from /proc/cpuinfo is for the last processor
   *                      reported in /proc/cpuinfo and thus can't be unambiguously attributed to that processor.
   *
   * @retval true if the chipset was found in the mapping and core clusters' leaders initialized with MIDR values.
   * @retval false if the chipset was not found in the mapping, or any consistency check failed.
   */
  static bool cpuinfo_arm_linux_detect_cluster_midr_by_chipset(
  	const struct cpuinfo_arm_chipset chipset[restrict static 1],
  	uint32_t clusters_count,
  	const uint32_t cluster_leaders[restrict static CLUSTERS_MAX],
  	uint32_t processors_count,
  	struct cpuinfo_arm_linux_processor processors[restrict static processors_count],
  	bool verify_midr)
  {
  	if (clusters_count <= CLUSTERS_MAX) {
  		for (uint32_t c = 0; c < CPUINFO_COUNT_OF(cluster_configs); c++) {
  			if (cluster_configs[c].model == chipset->model && cluster_configs[c].series == chipset->series) {
  				/* Verify that the total number of cores and clusters of cores matches expectation */
  				if (cluster_configs[c].cores != processors_count || cluster_configs[c].clusters != clusters_count) {
  					return false;
  				}
  
  				/* Verify that core cluster configuration matches expectation */
  				for (uint32_t cluster = 0; cluster < clusters_count; cluster++) {
  					const uint32_t cluster_leader = cluster_leaders[cluster];
  					if (cluster_configs[c].cluster_cores[cluster] != processors[cluster_leader].package_processor_count) {
  						return false;
  					}
  				}
  
  				if (verify_midr) {
  					/* Verify known parts of MIDR */
  					for (uint32_t cluster = 0; cluster < clusters_count; cluster++) {
  						const uint32_t cluster_leader = cluster_leaders[cluster];
  
  						/* Create a mask of known midr bits */
  						uint32_t midr_mask = 0;
  						if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_IMPLEMENTER) {
  							midr_mask |= CPUINFO_ARM_MIDR_IMPLEMENTER_MASK;
  						}
  						if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_VARIANT) {
  							midr_mask |= CPUINFO_ARM_MIDR_VARIANT_MASK;
  						}
  						if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_PART) {
  							midr_mask |= CPUINFO_ARM_MIDR_PART_MASK;
  						}
  						if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_REVISION) {
  							midr_mask |= CPUINFO_ARM_MIDR_REVISION_MASK;
  						}
  
  						/* Verify the bits under the mask */
  						if ((processors[cluster_leader].midr ^ cluster_configs[c].cluster_midr[cluster]) & midr_mask) {
  							cpuinfo_log_debug("parsed MIDR of cluster %08"PRIu32" does not match tabulated value %08"PRIu32,
  								processors[cluster_leader].midr, cluster_configs[c].cluster_midr[cluster]);
  							return false;
  						}
  					}
  				}
  
  				/* Assign MIDRs according to tabulated configurations */
  				for (uint32_t cluster = 0; cluster < clusters_count; cluster++) {
  					const uint32_t cluster_leader = cluster_leaders[cluster];
  					processors[cluster_leader].midr = cluster_configs[c].cluster_midr[cluster];
  					processors[cluster_leader].flags |= CPUINFO_ARM_LINUX_VALID_MIDR;
  					cpuinfo_log_debug("cluster %"PRIu32" MIDR = 0x%08"PRIx32, cluster, cluster_configs[c].cluster_midr[cluster]);
  				}
  				return true;
  			}
  		}
  	}
  	return false;
  }
  
  /*
   * Initializes MIDR for leaders of core clusters using a heuristic for big.LITTLE systems:
   * - If the only known MIDR is for the big core cluster, guess the matching MIDR for the LITTLE cluster.
   * - Estimate which of the clusters is big using maximum frequency, if known, otherwise using system processor ID.
   * - Initialize the MIDR for big and LITTLE core clusters using the guesstimates values.
   *
   * @param clusters_count - number of CPU core clusters detected in the SoC.
   * @param cluster_with_midr_count - number of CPU core clusters in the SoC with known MIDR values.
   * @param last_processor_with_midr - index of the last logical processor with known MIDR in the @p processors array.
   * @param cluster_leaders - indices of core clusters' leaders in the @p processors array.
   * @param[in,out] processors - array of logical processor descriptions with pre-parsed MIDR, maximum frequency,
   *                             and decoded core cluster (package_leader_id) information.
   *                             Upon successful return, processors[i].midr for all core clusters' leaders contains
   *                             the heuristically detected MIDR value.
   * @param verify_midr - indicated whether the function should check that the MIDR values to be assigned to leaders of
   *                      core clusters are consistent with known parts of their parsed values.
   *                      Set if to false if the only MIDR value parsed from /proc/cpuinfo is for the last processor
   *                      reported in /proc/cpuinfo and thus can't be unambiguously attributed to that processor.
   *
   * @retval true if this is a big.LITTLE system with only one known MIDR and the CPU core clusters' leaders were
   *              initialized with MIDR values.
   * @retval false if this is not a big.LITTLE system.
   */
  static bool cpuinfo_arm_linux_detect_cluster_midr_by_big_little_heuristic(
  	uint32_t clusters_count,
  	uint32_t cluster_with_midr_count,
  	uint32_t last_processor_with_midr,
  	const uint32_t cluster_leaders[restrict static CLUSTERS_MAX],
  	struct cpuinfo_arm_linux_processor processors[restrict static last_processor_with_midr],
  	bool verify_midr)
  {
  	if (clusters_count != 2 || cluster_with_midr_count != 1) {
  		/* Not a big.LITTLE system, or MIDR is known for both/neither clusters */
  		return false;
  	}
  
  	const uint32_t midr_flags =
  		(processors[processors[last_processor_with_midr].package_leader_id].flags & CPUINFO_ARM_LINUX_VALID_MIDR);
  	const uint32_t big_midr = processors[processors[last_processor_with_midr].package_leader_id].midr;
  	const uint32_t little_midr = midr_little_core_for_big(big_midr);
  
  	/* Default assumption: the first reported cluster is LITTLE cluster (this holds on most Linux kernels) */
  	uint32_t little_cluster_leader = cluster_leaders[0];
  	const uint32_t other_cluster_leader = cluster_leaders[1];
  	/* If maximum frequency is known for both clusters, assume LITTLE cluster is the one with lower frequency */
  	if (processors[little_cluster_leader].flags & processors[other_cluster_leader].flags & CPUINFO_LINUX_FLAG_MAX_FREQUENCY) {
  		if (processors[little_cluster_leader].max_frequency > processors[other_cluster_leader].max_frequency) {
  			little_cluster_leader = other_cluster_leader;
  		}
  	}
  
  	if (verify_midr) {
  		/* Verify known parts of MIDR */
  		for (uint32_t cluster = 0; cluster < clusters_count; cluster++) {
  			const uint32_t cluster_leader = cluster_leaders[cluster];
  
  			/* Create a mask of known midr bits */
  			uint32_t midr_mask = 0;
  			if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_IMPLEMENTER) {
  				midr_mask |= CPUINFO_ARM_MIDR_IMPLEMENTER_MASK;
  			}
  			if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_VARIANT) {
  				midr_mask |= CPUINFO_ARM_MIDR_VARIANT_MASK;
  			}
  			if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_PART) {
  				midr_mask |= CPUINFO_ARM_MIDR_PART_MASK;
  			}
  			if (processors[cluster_leader].flags & CPUINFO_ARM_LINUX_VALID_REVISION) {
  				midr_mask |= CPUINFO_ARM_MIDR_REVISION_MASK;
  			}
  
  			/* Verify the bits under the mask */
  			const uint32_t midr = (cluster_leader == little_cluster_leader) ? little_midr : big_midr;
  			if ((processors[cluster_leader].midr ^ midr) & midr_mask) {
  				cpuinfo_log_debug(
  					"parsed MIDR %08"PRIu32" of cluster leader %"PRIu32" is inconsistent with expected value %08"PRIu32,
  					processors[cluster_leader].midr, cluster_leader, midr);
  				return false;
  			}
  		}
  	}
  
  	for (uint32_t c = 0; c < clusters_count; c++) {
  		/* Skip cluster with already assigned MIDR */
  		const uint32_t cluster_leader = cluster_leaders[c];
  		if (bitmask_all(processors[cluster_leader].flags, CPUINFO_ARM_LINUX_VALID_MIDR)) {
  			continue;
  		}
  
  		const uint32_t midr = (cluster_leader == little_cluster_leader) ? little_midr : big_midr;
  		cpuinfo_log_info("assume processor %"PRIu32" to have MIDR %08"PRIx32, cluster_leader, midr);
  		/* To be consistent, we copy the MIDR entirely, rather than by parts */
  		processors[cluster_leader].midr = midr;
  		processors[cluster_leader].flags |= midr_flags;
  	}
  	return true;
  }
  
  /*
   * Initializes MIDR for leaders of core clusters in a single sequential scan:
   *  - Clusters preceeding the first reported MIDR value are assumed to have default MIDR value.
   *  - Clusters following any reported MIDR value to have that MIDR value.
   *
   * @param default_midr - MIDR value that will be assigned to cluster leaders preceeding any reported MIDR value.
   * @param processors_count - number of logical processor descriptions in the @p processors array.
   * @param[in,out] processors - array of logical processor descriptions with pre-parsed MIDR, maximum frequency,
   *                             and decoded core cluster (package_leader_id) information.
   *                             Upon successful return, processors[i].midr for all core clusters' leaders contains
   *                             the assigned MIDR value.
   */
  static void cpuinfo_arm_linux_detect_cluster_midr_by_sequential_scan(
  	uint32_t default_midr,
  	uint32_t processors_count,
  	struct cpuinfo_arm_linux_processor processors[restrict static processors_count])
  {
  	uint32_t midr = default_midr;
  	for (uint32_t i = 0; i < processors_count; i++) {
  		if (bitmask_all(processors[i].flags, CPUINFO_LINUX_FLAG_VALID)) {
  			if (processors[i].package_leader_id == i) {
  				if (bitmask_all(processors[i].flags, CPUINFO_ARM_LINUX_VALID_MIDR)) {
  					midr = processors[i].midr;	
  				} else {
  					cpuinfo_log_info("assume processor %"PRIu32" to have MIDR %08"PRIx32, i, midr);
  					/* To be consistent, we copy the MIDR entirely, rather than by parts */
  					processors[i].midr = midr;
  					processors[i].flags |= CPUINFO_ARM_LINUX_VALID_MIDR;
  				}
  			}
  		}
  	}
  }
  
  /*
   * Detects MIDR of each CPU core clusters' leader.
   *
   * @param[in] chipset - chipset (SoC) name information.
   * @param max_processors - number of processor descriptions in the @p processors array.
   * @param usable_processors - number of processor descriptions in the @p processors array with both POSSIBLE and
   *                            PRESENT flags.
   * @param[in,out] processors - array of logical processor descriptions with pre-parsed MIDR, maximum frequency,
   *                             and decoded core cluster (package_leader_id) information.
   *                             Upon return, processors[i].midr for all clusters' leaders contains the MIDR value.
   *
   * @returns The number of core clusters
   */
  uint32_t cpuinfo_arm_linux_detect_cluster_midr(
  	const struct cpuinfo_arm_chipset chipset[restrict static 1],
  	uint32_t max_processors,
  	uint32_t usable_processors,
  	struct cpuinfo_arm_linux_processor processors[restrict static max_processors])
  {
  	uint32_t clusters_count = 0;
  	uint32_t cluster_leaders[CLUSTERS_MAX];
  	uint32_t last_processor_in_cpuinfo = max_processors;
  	uint32_t last_processor_with_midr = max_processors;
  	uint32_t processors_with_midr_count = 0;
  	for (uint32_t i = 0; i < max_processors; i++) {
  		if (bitmask_all(processors[i].flags, CPUINFO_LINUX_FLAG_VALID)) {
  			if (processors[i].flags & CPUINFO_ARM_LINUX_VALID_PROCESSOR) {
  				last_processor_in_cpuinfo = i;
  			}
  			if (bitmask_all(processors[i].flags, CPUINFO_ARM_LINUX_VALID_IMPLEMENTER | CPUINFO_ARM_LINUX_VALID_PART)) {
  				last_processor_with_midr = i;
  				processors_with_midr_count += 1;
  			}
  			const uint32_t group_leader = processors[i].package_leader_id;
  			if (group_leader == i) {
  				if (clusters_count < CLUSTERS_MAX) {
  					cluster_leaders[clusters_count] = i;
  				}
  				clusters_count += 1;
  			} else {
  				/* Copy known bits of information to cluster leader */
  
  				if ((processors[i].flags & ~processors[group_leader].flags) & CPUINFO_LINUX_FLAG_MAX_FREQUENCY) {
  					processors[group_leader].max_frequency = processors[i].max_frequency;
  					processors[group_leader].flags |= CPUINFO_LINUX_FLAG_MAX_FREQUENCY;
  				}
  				if (!bitmask_all(processors[group_leader].flags, CPUINFO_ARM_LINUX_VALID_MIDR) &&
  					bitmask_all(processors[i].flags, CPUINFO_ARM_LINUX_VALID_MIDR))
  				{
  					processors[group_leader].midr = processors[i].midr;
  					processors[group_leader].flags |= CPUINFO_ARM_LINUX_VALID_MIDR;
  				}
  			}
  		}
  	}
  	cpuinfo_log_debug("detected %"PRIu32" core clusters", clusters_count);
  
  	/*
  	 * Two relations between reported /proc/cpuinfo information, and cores is possible:
  	 * - /proc/cpuinfo reports information for all or some of the cores below the corresponding
  	 *   "processor : <number>" lines. Information on offline cores may be missing.
  	 * - /proc/cpuinfo reports information only once, after all "processor : <number>" lines.
  	 *   The reported information may relate to processor #0 or to the processor which
  	 *   executed the system calls to read /proc/cpuinfo. It is also indistinguishable
  	 *   from /proc/cpuinfo reporting information only for the last core (e.g. if all other
  	 *   cores are offline).
  	 *
  	 * We detect the second case by checking if /proc/cpuinfo contains valid MIDR only for one,
  	 * last reported, processor. Note, that the last reported core may be not the last
  	 * present & possible processor, as /proc/cpuinfo may non-report high-index offline cores.
  	 */
  	if (processors_with_midr_count == 1 && last_processor_in_cpuinfo == last_processor_with_midr && clusters_count > 1) {
  		/*
  		 * There are multiple core clusters, but /proc/cpuinfo reported MIDR only for one
  		 * processor, and we don't even know which logical processor this information refers to.
  		 *
  		 * We make three attempts to detect MIDR for all clusters:
  		 * 1. Search tabulated MIDR values for chipsets which have heterogeneous clusters and ship with Linux
  		 *    kernels which do not always report all cores in /proc/cpuinfo. If found, use the tabulated values.
  		 * 2. For systems with 2 clusters and MIDR known for one cluster, assume big.LITTLE configuration,
  		 *    and estimate MIDR for the other cluster under assumption that MIDR for the big cluster is known.
  		 * 3. Initialize MIDRs for all core clusters to the only parsed MIDR value.
  		 */
  		cpuinfo_log_debug("the only reported MIDR can not be attributed to a particular processor");
  
  		if (cpuinfo_arm_linux_detect_cluster_midr_by_chipset(
  			chipset, clusters_count, cluster_leaders, usable_processors, processors, false))
  		{
  			return clusters_count;
  		}
  
  		/* Try big.LITTLE heuristic */
  		if (cpuinfo_arm_linux_detect_cluster_midr_by_big_little_heuristic(
  			clusters_count, 1, last_processor_with_midr,
  			cluster_leaders, processors, false))
  		{
  			return clusters_count;
  		}
  
  		/* Fall back to sequential initialization of MIDR values for core clusters */
  		cpuinfo_arm_linux_detect_cluster_midr_by_sequential_scan(
  			processors[processors[last_processor_with_midr].package_leader_id].midr,
  			max_processors, processors);
  	} else if (processors_with_midr_count < usable_processors) {
  		/*
  		 * /proc/cpuinfo reported MIDR only for some processors, and probably some core clusters do not have MIDR
  		 * for any of the cores. Check if this is the case.
  		 */
  		uint32_t clusters_with_midr_count = 0;
  		for (uint32_t i = 0; i < max_processors; i++) {
  			if (bitmask_all(processors[i].flags, CPUINFO_LINUX_FLAG_VALID | CPUINFO_ARM_LINUX_VALID_MIDR)) {
  				if (processors[i].package_leader_id == i) {
  					clusters_with_midr_count += 1;
  				}
  			}
  		}
  
  		if (clusters_with_midr_count < clusters_count) {
  			/*
  			 * /proc/cpuinfo reported MIDR only for some clusters, need to reconstruct others.
  			 * We make three attempts to detect MIDR for clusters without it:
  			 * 1. Search tabulated MIDR values for chipsets which have heterogeneous clusters and ship with Linux
  			 *    kernels which do not always report all cores in /proc/cpuinfo. If found, use the tabulated values.
  			 * 2. For systems with 2 clusters and MIDR known for one cluster, assume big.LITTLE configuration,
  			 *    and estimate MIDR for the other cluster under assumption that MIDR for the big cluster is known.
  			 * 3. Initialize MIDRs for core clusters in a single sequential scan:
  			 *    - Clusters preceeding the first reported MIDR value are assumed to have the last reported MIDR value.
  			 *    - Clusters following any reported MIDR value to have that MIDR value.
  			 */
  			
  			if (cpuinfo_arm_linux_detect_cluster_midr_by_chipset(
  				chipset, clusters_count, cluster_leaders, usable_processors, processors, true))
  			{
  				return clusters_count;
  			}
  
  			if (last_processor_with_midr != max_processors) {
  				/* Try big.LITTLE heuristic */
  				if (cpuinfo_arm_linux_detect_cluster_midr_by_big_little_heuristic(
  					clusters_count, processors_with_midr_count, last_processor_with_midr,
  					cluster_leaders, processors, true))
  				{
  					return clusters_count;
  				}
  
  				/* Fall back to sequential initialization of MIDR values for core clusters */
  				cpuinfo_arm_linux_detect_cluster_midr_by_sequential_scan(
  					processors[processors[last_processor_with_midr].package_leader_id].midr,
  					max_processors, processors);
  			}
  		}
  	}
  	return clusters_count;
  }
  