Beispiel #1
0
int envy_bios_parse_power_boost(struct envy_bios *bios) {
	struct envy_bios_power_boost *boost = &bios->power.boost;
	int i, j, err = 0;

	if (!boost->offset)
		return -EINVAL;

	bios_u8(bios, boost->offset + 0x0, &boost->version);
	switch(boost->version) {
	case 0x11:
		err |= bios_u8(bios, boost->offset + 0x1, &boost->hlen);
		err |= bios_u8(bios, boost->offset + 0x2, &boost->rlen);
		err |= bios_u8(bios, boost->offset + 0x3, &boost->ssz);
		err |= bios_u8(bios, boost->offset + 0x4, &boost->snr);
		err |= bios_u8(bios, boost->offset + 0x5, &boost->entriesnum);
		boost->valid = !err;
		break;
	default:
		ENVY_BIOS_ERR("Unknown BOOST table version 0x%x\n", boost->version);
		return -EINVAL;
	};

	boost->entries = malloc(boost->entriesnum * sizeof(struct envy_bios_power_boost_entry));

	for (i = 0; i < boost->entriesnum; i++) {
		uint16_t data = boost->offset + boost->hlen + i * (boost->rlen + (boost->snr * boost->ssz));

		uint16_t tmp;
		err |= bios_u16(bios, data + 0x0, &tmp);
		err |= bios_u16(bios, data + 0x2, &boost->entries[i].min);
		err |= bios_u16(bios, data + 0x4, &boost->entries[i].max);

		boost->entries[i].offset = data;
		boost->entries[i].pstate = (tmp & 0x01e0) >> 5;

		boost->entries[i].entries = malloc(boost->snr * sizeof(struct envy_bios_power_boost_subentry));

		for (j = 0; j < boost->snr; j++) {
			struct envy_bios_power_boost_subentry *sub = &boost->entries[i].entries[j];
			uint16_t sdata = data + boost->rlen + j * boost->ssz;

			sub->offset = sdata;
			bios_u8(bios, sdata + 0x0, &sub->domain);
			bios_u8(bios, sdata + 0x1, &sub->percent);
			bios_u16(bios, sdata + 0x2, &sub->min);
			bios_u16(bios, sdata + 0x4, &sub->max);
		}
	}

	return 0;
}
Beispiel #2
0
int envy_bios_parse_power_cstep(struct envy_bios *bios) {
	struct envy_bios_power_cstep *cstep = &bios->power.cstep;
	int i, err = 0;

	if (!cstep->offset)
		return -EINVAL;

	bios_u8(bios, cstep->offset + 0x0, &cstep->version);
	switch(cstep->version) {
	case 0x10:
		err |= bios_u8(bios, cstep->offset + 0x1, &cstep->hlen);
		err |= bios_u8(bios, cstep->offset + 0x2, &cstep->rlen);
		err |= bios_u8(bios, cstep->offset + 0x3, &cstep->entriesnum);
		err |= bios_u8(bios, cstep->offset + 0x4, &cstep->ssz);
		err |= bios_u8(bios, cstep->offset + 0x5, &cstep->snr);
		cstep->valid = !err;
		break;
	default:
		ENVY_BIOS_ERR("Unknown CSTEP table version 0x%x\n", cstep->version);
		return -EINVAL;
	};

	assert(cstep->entriesnum <= (sizeof(cstep->ent1) / sizeof(struct envy_bios_power_cstep_entry1)));
	for (i = 0; i < cstep->entriesnum; i++) {
		uint16_t data = cstep->offset + cstep->hlen + i * cstep->rlen;

		uint16_t tmp;
		err |= bios_u16(bios, data + 0x0, &tmp);

		cstep->ent1[i].offset = data;
		cstep->ent1[i].pstate = (tmp & 0x01e0) >> 5;
		bios_u8(bios, data + 0x3, &cstep->ent1[i].index);
	}

	cstep->ent2 = malloc(cstep->snr * sizeof(struct envy_bios_power_cstep_entry2));
	memset(cstep->ent2, 0x0, cstep->snr * sizeof(struct envy_bios_power_cstep_entry2));
	for (i = 0; i < cstep->snr; i++) {
		uint16_t data = cstep->offset + cstep->hlen + (cstep->entriesnum * cstep->rlen) + (i * cstep->ssz);

		cstep->ent2[i].offset = data;
		bios_u16(bios, data + 0x0, &cstep->ent2[i].freq);
		bios_u8(bios, data + 0x2, &cstep->ent2[i].unkn[0]);
		bios_u8(bios, data + 0x3, &cstep->ent2[i].unkn[1]);
		bios_u8(bios, data + 0x4, &cstep->ent2[i].voltage);
		cstep->ent2[i].valid = (cstep->ent2[i].freq > 0);
	}

	return 0;
}
Beispiel #3
0
int envy_bios_parse_power_base_clock(struct envy_bios *bios) {
	struct envy_bios_power_base_clock *bc = &bios->power.base_clock;
	int i, j, err = 0;

	if (!bc->offset)
		return -EINVAL;

	bios_u8(bios, bc->offset + 0x0, &bc->version);
	switch(bc->version) {
	case 0x10:
		err |= bios_u8(bios, bc->offset + 0x1, &bc->hlen);
		err |= bios_u8(bios, bc->offset + 0x2, &bc->rlen);
		err |= bios_u8(bios, bc->offset + 0x3, &bc->selen);
		err |= bios_u8(bios, bc->offset + 0x4, &bc->secount);
		err |= bios_u8(bios, bc->offset + 0x5, &bc->entriesnum);
		bc->valid = !err;

		if (bc->valid) {
			bios_u8(bios, bc->offset + 0x0f, &bc->base_entry);
			bios_u8(bios, bc->offset + 0x10, &bc->boost_entry);
			bios_u8(bios, bc->offset + 0x11, &bc->tdp_entry);
		} else {
			return -EINVAL;
		}

		break;
	default:
		ENVY_BIOS_ERR("BASE CLOCKS table version 0x%x\n", bc->version);
		return -EINVAL;
	};

	err = 0;
	bc->entries = malloc(bc->entriesnum * sizeof(struct envy_bios_power_base_clock_entry));
	for (i = 0; i < bc->entriesnum; i++) {
		struct envy_bios_power_base_clock_entry *bce = &bc->entries[i];

		bce->offset = bc->offset + bc->hlen + i * (bc->rlen + (bc->selen * bc->secount));
		bios_u8(bios, bce->offset, &bce->pstate);
		bios_u16(bios, bce->offset + 0x1, &bce->unk0);
		bios_u16(bios, bce->offset + 0x3, &bce->unk1);
		bce->clock = malloc(bc->secount * sizeof(uint16_t));
		for (j = 0; j < bc->secount; j++)
			bios_u16(bios, bce->offset + 0x5 + j, &bce->clock[j]);
	}

	return 0;
}
Beispiel #4
0
static int
parse_at(struct envy_bios *bios, struct envy_bios_mem *mem,
	     int idx, int offset, const char ** name)
{
	struct M_known_tables m1_tbls[] = {
		/* { 0x00, &mem->trestrict, "RESET" }, */
		/* 0x01 Memory Strap Data Count */
		{ 0x03, &mem->trestrict, "RESTRICT" },
		/* { 0x05, &mem->, "DATA VREF" }, */
		/* { 0x07, &mem->, "DATA DQS" }, */
		/* { 0x09, &mem->, "DATA DLCELL ON" }, */
		/* { 0x0B, &mem->, "DATA DLCELL OFF" }, */
	};
	struct M_known_tables m2_tbls[] = {
		{ 0x01, &mem->trestrict, "RESTRICT" },
		{ 0x03, &mem->type.offset, "TYPE" },
		{ 0x05, &mem->train.offset, "TRAIN" },
		{ 0x09, &mem->train_ptrn.offset, "TRAIN PATTERN" },
		{ 0x0d, &mem->unk0d.offset, "UNK0D" },
	};
	struct M_known_tables *tbls;
	int entries_count = 0;

	if (mem->bit->version == 0x1) {
		tbls = m1_tbls;
		entries_count = (sizeof(m1_tbls) / sizeof(struct M_known_tables));
	} else if (mem->bit->version == 0x2) {
		tbls = m2_tbls;
		entries_count = (sizeof(m2_tbls) / sizeof(struct M_known_tables));
	} else
		return -EINVAL;

	/* either we address by offset or idx */
	if (idx != -1 && offset != -1)
		return -EINVAL;

	/* lookup the index by the table's offset */
	if (offset > -1) {
		idx = 0;
		while (idx < entries_count && tbls[idx].offset != offset)
			idx++;
	}

	/* check the index */
	if (idx < 0 || idx >= entries_count)
		return -ENOENT;

	/* check the table has the right size */
	if (tbls[idx].offset + 2 > mem->bit->t_len)
		return -ENOENT;

	if (name)
		*name = tbls[idx].name;

	return bios_u16(bios,
			mem->bit->t_offset + tbls[idx].offset,
			tbls[idx].ptr);
}
Beispiel #5
0
int envy_bios_parse_bit_2 (struct envy_bios *bios, struct envy_bios_bit_entry *bit) {
	struct envy_bios_i2cscript *i2cscript = &bios->i2cscript;
	i2cscript->bit = bit;
	int wantlen = 4;
	if (bit->t_len < wantlen) {
		ENVY_BIOS_ERR("I2C script table too short: %d < %d\n", bit->t_len, wantlen);
		return -EINVAL;
	}
	if (bit->t_len > wantlen)
		ENVY_BIOS_WARN("I2C script table longer than expected: %d > %d\n", bit->t_len, wantlen);
	int err = 0;
	err |= bios_u16(bios, bit->t_offset+0, &i2cscript->unk00);
	err |= bios_u16(bios, bit->t_offset+2, &i2cscript->script);
	if (err)
		return -EFAULT;
	i2cscript->valid = 1;
	return 0;
}
Beispiel #6
0
int envy_bios_parse_power_fan(struct envy_bios *bios) {
	struct envy_bios_power_fan *fan = &bios->power.fan;
	uint16_t data;
	int err = 0;

	if (!fan->offset)
		return -EINVAL;

	bios_u8(bios, fan->offset + 0x0, &fan->version);
	switch(fan->version) {
	case 0x10:
		err |= bios_u8(bios, fan->offset + 0x1, &fan->hlen);
		err |= bios_u8(bios, fan->offset + 0x2, &fan->rlen);
		err |= bios_u8(bios, fan->offset + 0x3, &fan->entriesnum);

		fan->valid = !err;
		break;
	default:
		ENVY_BIOS_ERR("Unknown FAN table version 0x%x\n", fan->version);
		return -EINVAL;
	};

	/* go to the first entry */
	data = fan->offset + fan->hlen;

	bios_u8(bios, data + 0x00, &fan->type);
	bios_u8(bios, data + 0x02, &fan->duty_min);
	bios_u8(bios, data + 0x03, &fan->duty_max);
	/* 0x10 == constant to 9? */
	bios_u32(bios, data + 0x0b, &fan->divisor); fan->divisor &= 0xffffff;
	bios_u16(bios, data + 0x0e, &fan->unk0e); /* looks like the fan bump delay */
	bios_u16(bios, data + 0x10, &fan->unk10); /* looks like the fan slow down delay */
	bios_u16(bios, data + 0x14, &fan->unboost_unboost_ms);
	bios_u8(bios, data + 0x17, &fan->duty_boosted); /* threshold = 96 °C */

	/* temp fan bump min = 45°C */
	/* temp fan max = 95°C */

	return 0;
}
Beispiel #7
0
int
envy_bios_parse_mem_train (struct envy_bios *bios) {
	struct envy_bios_mem_train *mt = &bios->mem.train;
	if (!mt->offset)
		return 0;
	int err = 0;
	err |= bios_u8(bios, mt->offset, &mt->version);
	err |= bios_u8(bios, mt->offset+1, &mt->hlen);
	err |= bios_u8(bios, mt->offset+2, &mt->rlen);
	err |= bios_u8(bios, mt->offset+3, &mt->subentrylen);
	err |= bios_u8(bios, mt->offset+4, &mt->subentries);
	err |= bios_u8(bios, mt->offset+5, &mt->entriesnum);
	if (err)
		return -EFAULT;
	bios_u16(bios, mt->offset+6, &mt->mclk);

	envy_bios_block(bios, mt->offset, mt->hlen + mt->rlen * mt->entriesnum, "MEM TRAIN", -1);
	mt->entries = calloc(mt->entriesnum, sizeof *mt->entries);
	if (!mt->entries)
		return -ENOMEM;
	int i;
	for (i = 0; i < mt->entriesnum; i++) {
		struct envy_bios_mem_train_entry *entry = &mt->entries[i];
		entry->offset = mt->offset + mt->hlen + ((mt->rlen + mt->subentries * mt->subentrylen) * i);
		err |= bios_u8(bios, entry->offset, &entry->u00);
		if (mt->subentries > sizeof(entry->subentry)) {
			mt->subentries = sizeof(entry->subentry);
			ENVY_BIOS_ERR("Error when parsing mem train: subentries = %d > %zu\n", mt->subentries, sizeof(entry->subentry));
			return -EFAULT;
		}
		int j;
		for (j = 0; j < mt->subentries; j++) {
			err |= bios_u8(bios, entry->offset+j+1, &entry->subentry[j]);
		}
		if (err)
			return -EFAULT;
	}
	mt->valid = 1;
	return 0;
}
Beispiel #8
0
void envy_bios_print_bit_P (struct envy_bios *bios, FILE *out, unsigned mask) {
	struct envy_bios_power *power = &bios->power;
	const char *name;
	uint16_t addr;
	int ret = 0, i = 0;
	
	if (!power->bit || !(mask & ENVY_BIOS_PRINT_PERF))
		return;

	fprintf(out, "BIT table 'P' at 0x%x, version %i\n", 
		power->bit->offset, power->bit->version);

	for (i = 0; i < power->bit->t_len; i+=2) {
		ret = bios_u16(bios, power->bit->t_offset + i, &addr);
		if (!ret && addr) {
			name = "UNKNOWN";
			ret = parse_at(bios, power, -1, i, &name);
			fprintf(out, "0x%02x: 0x%x => %s TABLE\n", i, addr, name);
		}
	}
	
	fprintf(out, "\n");
}
Beispiel #9
0
int envy_bios_parse_bit (struct envy_bios *bios) {
	struct envy_bios_bit *bit = &bios->bit;
	if (!bit->offset)
		return 0;
	int err = 0;
	err |= bios_u8(bios, bit->offset+7, &bit->version);
	err |= bios_u8(bios, bit->offset+8, &bit->hlen);
	err |= bios_u8(bios, bit->offset+9, &bit->rlen);
	err |= bios_u8(bios, bit->offset+10, &bit->entriesnum);
	if (err)
		return -EFAULT;
	envy_bios_block(bios, bit->offset, bit->hlen + bit->rlen * bit->entriesnum, "BIT", -1);
	uint8_t checksum = 0;
	int i;
	for (i = 0; i < bit->hlen; i++) {
		uint8_t byte;
		err |= bios_u8(bios, bit->offset+i, &byte);
		if (err)
			return -EFAULT;
		checksum += byte;
	}
	if (checksum) {
		ENVY_BIOS_ERR("BIT table checksum mismatch\n");
		return -EINVAL;
	}
	int wanthlen = 12;
	int wantrlen = 6;
	switch (bit->version) {
		case 1:
			break;
		default:
			ENVY_BIOS_ERR("Unknown BIT table version %d\n", bit->version);
			return -EINVAL;
	}
	if (bit->hlen < wanthlen) {
		ENVY_BIOS_ERR("BIT table header too short [%d < %d]\n", bit->hlen, wanthlen);
		return -EINVAL;
	}
	if (bit->rlen < wantrlen) {
		ENVY_BIOS_ERR("BIT table record too short [%d < %d]\n", bit->rlen, wantrlen);
		return -EINVAL;
	}
	if (bit->hlen > wanthlen) {
		ENVY_BIOS_WARN("BIT table header longer than expected [%d > %d]\n", bit->hlen, wanthlen);
	}
	if (bit->rlen > wantrlen) {
		ENVY_BIOS_WARN("BIT table record longer than expected [%d > %d]\n", bit->rlen, wantrlen);
	}
	bit->entries = calloc(bit->entriesnum, sizeof *bit->entries);
	if (!bit->entries)
		return -ENOMEM;
	for (i = 0; i < bit->entriesnum; i++) {
		struct envy_bios_bit_entry *entry = &bit->entries[i];
		entry->offset = bit->offset + bit->hlen + bit->rlen * i;
		err |= bios_u8(bios, entry->offset+0, &entry->type);
		err |= bios_u8(bios, entry->offset+1, &entry->version);
		err |= bios_u16(bios, entry->offset+2, &entry->t_len);
		err |= bios_u16(bios, entry->offset+4, &entry->t_offset);
		if (err)
			return -EFAULT;
		entry->is_unk = 1;
		if (entry->type != 'b' && entry->t_len)
			envy_bios_block(bios, entry->t_offset, entry->t_len, "BIT", entry->type);
	}
	int j;
	/* iterate over BIT tables by type first - some types of tables have to be parsed before others, notably 'i'. */
	for (j = 0; bit_types[j].parse; j++) {
		for (i = 0; i < bit->entriesnum; i++) {
			struct envy_bios_bit_entry *entry = &bit->entries[i];
			if (entry->type == bit_types[j].type && entry->version == bit_types[j].version) {
				if (bit_types[j].parse(bios, entry))
					ENVY_BIOS_ERR("Failed to parse BIT table '%c' at 0x%04x version %d\n", entry->type, entry->t_offset, entry->version);
				else
					entry->is_unk = 0;
			}
		}
	}
	bit->valid = 1;
	return 0;
}
Beispiel #10
0
static void print_nv01_init_script(struct envy_bios *bios, FILE *out, unsigned offset, unsigned mask) {
	unsigned len;
	uint8_t op;
	uint8_t arg8_0, arg8_1, arg8_2;
	uint16_t arg16_0;
	uint32_t arg32_0, arg32_1, arg32_2;
	int err = 0;
	if (!(mask & ENVY_BIOS_PRINT_SCRIPTS))
		return;
	fprintf(out, "Init script at 0x%x:\n", offset);
	while (1) {
		if (bios_u8(bios, offset, &op)) {
			ENVY_BIOS_ERR("Init script out of bounds!\n");
			return;
		}
		switch (op) {
		case 0x6e:	/* NV01+ */
			len = 13;
			err |= bios_u32(bios, offset+1, &arg32_0);
			err |= bios_u32(bios, offset+5, &arg32_1);
			err |= bios_u32(bios, offset+9, &arg32_2);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tMMIO_MASK 0x%06x &= 0x%08x |= 0x%08x\n", arg32_0, arg32_1, arg32_2);
			break;
		case 0x7a:	/* NV03+ */
			len = 9;
			err |= bios_u32(bios, offset+1, &arg32_0);
			err |= bios_u32(bios, offset+5, &arg32_1);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tMMIO_WR 0x%06x <= 0x%08x\n", arg32_0, arg32_1);
			break;
		case 0x77:	/* NV03+ */
			len = 7;
			err |= bios_u32(bios, offset+1, &arg32_0);
			err |= bios_u16(bios, offset+5, &arg16_0);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tMMIO_WR16 0x%06x <= 0x%04x\n", arg32_0, arg16_0);
			break;
		case 0x79:	/* NV03+ */
			len = 13;
			err |= bios_u32(bios, offset+1, &arg32_0);
			err |= bios_u32(bios, offset+5, &arg32_1);
			err |= bios_u32(bios, offset+9, &arg32_2);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tMMIO_WR_CRYSTAL 0x%06x <= 0x%08x / 0x%08x\n", arg32_0, arg32_1, arg32_2);
			break;
		case 0x74:	/* NV03+ */
			len = 3;
			err |= bios_u16(bios, offset+1, &arg16_0);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tUSLEEP %d\n", arg16_0);
			break;
		case 0x69:	/* NV03+ */
			len = 5;
			err |= bios_u16(bios, offset+1, &arg16_0);
			err |= bios_u8(bios, offset+3, &arg8_0);
			err |= bios_u8(bios, offset+4, &arg8_1);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tIO_MASK 0x%04x &= 0x%02x |= 0x%02x\n", arg16_0, arg8_0, arg8_1);
			break;
		case 0x78:	/* NV03+ */
			len = 6;
			err |= bios_u16(bios, offset+1, &arg16_0);
			err |= bios_u8(bios, offset+3, &arg8_0);
			err |= bios_u8(bios, offset+4, &arg8_1);
			err |= bios_u8(bios, offset+5, &arg8_2);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tIOIDX_MASK 0x%04x[0x%02x] &= 0x%02x |= 0x%02x\n", arg16_0, arg8_0, arg8_1, arg8_2);
			break;
		case 0x6d:	/* NV03+ */
			len = 2;
			err |= bios_u8(bios, offset+1, &arg8_0);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tIF_MEM_SIZE 0x%02x\n", arg8_0);
			break;
		case 0x73:	/* NV03+ */
			len = 9;
			err |= bios_u32(bios, offset+1, &arg32_0);
			err |= bios_u32(bios, offset+5, &arg32_1);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tIF_STRAPS & 0x%08x == 0x%08x\n", arg32_0, arg32_1);
			break;
		case 0x72:	/* NV03+ */
			len = 1;
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tRESUME\n");
			break;
		case 0x63:	/* NV03+ */
			len = 1;
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tCOMPUTE_MEM\n");
			break;
		case 0x71:	/* NV01+ */
			len = 1;
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tQUIT\n");
			fprintf(out, "\n");
			return;
		case 0x70:	/* NV01:NV03 */
			len = 7;
			err |= bios_u16(bios, offset+1, &arg16_0);
			err |= bios_u32(bios, offset+3, &arg32_0);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tDAC_PLL 0x%04x <= 0x%08x\n", arg16_0, arg32_0);
			break;
		case 0x64:	/* NV01:NV03 */
			len = 5;
			err |= bios_u16(bios, offset+1, &arg16_0);
			err |= bios_u8(bios, offset+3, &arg8_0);
			err |= bios_u8(bios, offset+4, &arg8_1);
			if (err) {
				ENVY_BIOS_ERR("Init script out of bounds!\n");
				return;
			}
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tDAC_MASK 0x%04x &= 0x%02x |= 0x%02x\n", arg16_0, arg8_0, arg8_1);
			break;
		case 0xff:	/* NV01:NV03 */
			len = 1;
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\tQUIT\n");
			fprintf(out, "\n");
			return;
		default:
			len = 1;
			dump_hex_script(bios, out, offset, len);
			fprintf(out, "\n");
			ENVY_BIOS_ERR("Unknown op 0x%02x in init script!\n", op);
			return;
		}
		offset += len;
	}
}
Beispiel #11
0
static int parse_at(struct envy_bios *bios, struct envy_bios_power *power,
	     int idx, int offset, const char ** name)
{
	struct P_known_tables p1_tbls[] = {
		{ 0x00, &power->perf.offset, "PERFORMANCE" },
		{ 0x04, &power->timing.offset, "MEMORY TIMINGS" },
		{ 0x0c, &power->therm.offset, "THERMAL" },
		{ 0x10, &power->volt.offset, "VOLTAGE" },
		{ 0x15, &power->unk18.offset, "POWER UNK18" }
	};
	struct P_known_tables p2_tbls[] = {
		{ 0x00, &power->perf.offset, "PERFORMANCE" },
		{ 0x04, &power->timing_map.offset, "MEMORY TIMINGS MAPPING" },
		{ 0x08, &power->timing.offset, "MEMORY TIMINGS" },
		{ 0x0c, &power->volt.offset, "VOLTAGE" },
		{ 0x10, &power->therm.offset, "THERMAL"  },
		{ 0x14, &power->unk14.offset, "UNK14"  },
		{ 0x18, &power->unk18.offset, "POWER UNK18" },
		{ 0x1c, &power->unk1c.offset, "POWER UNK1C" },
		{ 0x20, &power->volt_map.offset, "VOLT MAPPING" },
		{ 0x24, &power->unk24.offset, "POWER UNK24" },
		{ 0x28, &power->sense.offset, "POWER SENSE" },
		{ 0x2c, &power->budget.offset, "POWER BUDGET" },
		{ 0x30, &power->boost.offset, "BOOST" },
		{ 0x34, &power->cstep.offset, "CSTEP" },
		{ 0x38, &power->base_clock.offset, "POWER BASE CLOCK" },
		{ 0x3c, &power->unk3c.offset, "POWER UNK3C" },
		{ 0x40, &power->unk40.offset, "POWER UNK40" },
		{ 0x44, &power->unk44.offset, "POWER UNK44" },
		{ 0x48, &power->unk48.offset, "POWER UNK48" },
		{ 0x4c, &power->unk4c.offset, "POWER UNK4C" },
		{ 0x50, &power->unk50.offset, "POWER UNK50" },
		{ 0x54, &power->unk54.offset, "POWER UNK54" },
		{ 0x58, &power->fan.offset, "POWER FAN" },
		{ 0x5c, &power->unk5c.offset, "POWER UNK5C" },
		{ 0x60, &power->unk60.offset, "POWER UNK60" },
		{ 0x64, &power->unk64.offset, "POWER UNK64" }
	};
	struct P_known_tables *tbls;
	int entries_count = 0;

	if (power->bit->version == 0x1) {
		tbls = p1_tbls;
		entries_count = (sizeof(p1_tbls) / sizeof(struct P_known_tables));
	} else if (power->bit->version == 0x2) {
		tbls = p2_tbls;
		entries_count = (sizeof(p2_tbls) / sizeof(struct P_known_tables));
	} else
		return -EINVAL;

	/* either we address by offset or idx */
	if (idx != -1 && offset != -1)
		return -EINVAL;

	/* lookup the index by the table's offset */
	if (offset > -1) {
		idx = 0;
		while (idx < entries_count && tbls[idx].offset != offset)
			idx++;
	}

	/* check the index */
	if (idx < 0 || idx >= entries_count)
		return -ENOENT;
	
	/* check the table has the right size */
	if (tbls[idx].offset + 2 > power->bit->t_len)
		return -ENOENT;
	
	if (name)
		*name = tbls[idx].name;
	
	return bios_u16(bios, 
			power->bit->t_offset + tbls[idx].offset, 
			tbls[idx].ptr);
}