Re: [PATCH v18 2/2] drm/i915/selftests: Introduce live tests of private PAT management

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, 2017-09-22 at 01:28 +0800, Zhi Wang wrote:
> Introduce two live tests of private PAT management:
> 
> igt_ppat_init - This test is to check if all the PPAT configurations are
> written into HW.
> 
> igt_ppat_get - This test performs several sub-tests on intel_ppat_get()
> and intel_ppat_put().
> 
> The "perfect match" test case will try to get a PPAT entry with an existing
> value, then check if the returned PPAT entry is the same one.
> 
> The "alloc entries" test case will run out of PPAT table, and check if all
> the requested values are put into the newly allocated PPAT entries.
> 
> The negative test case will try to generate a new PPAT value, and get it
> when PPAT table is full.
> 
> The "partial match" test case will generate a parital matched value from
> the existing PPAT table and try to match it.
> 
> The "re-alloc" test case will try to free and then allocate a new entry
> when the PPAT table is full.
> 
> The "put entries" test case will free all the PPAT entries that allocated
> in "alloc entries" test case. It will check if the values of freed PPAT
> entries turn into ppat->clear_value.
> 
> v18:
> 
> - Refine the test to catch a corner case.
> 
> v10:
> 
> - Refine code structure.
> - Introduce "re-alloc" test case. (Chris)
> 
> v9:
> 
> - Refine generate_new_value(). (Chris)
> - Refine failure output. (Chris)
> - Refine test flow of "perfect match". (Chris)
> - Introduce another negative test case after "partial match". (Chris)
> 
> v8:
> 
> - Remove noisy output. (Chris)
> - Add negative test case. (Chris)
> 
> Cc: Ben Widawsky <benjamin.widawsky@xxxxxxxxx>
> Cc: Rodrigo Vivi <rodrigo.vivi@xxxxxxxxx>
> Cc: Joonas Lahtinen <joonas.lahtinen@xxxxxxxxxxxxxxx>
> Suggested-by: Chris Wilson <chris@xxxxxxxxxxxxxxxxxx>
> Signed-off-by: Zhi Wang <zhi.a.wang@xxxxxxxxx>

<SNIP>

> +static int igt_ppat_check(void *arg)
> +{
> +	struct drm_i915_private *i915 = arg;
> +	int ret;
> +
> +	if (!i915->ppat.max_entries)
> +		return 0;
> +
> +	if (INTEL_GEN(i915) >= 10)
> +		ret = check_cnl_ppat(i915);
> +	else
> +		ret = check_bdw_ppat(i915);
> +
> +	if (ret)
> +		pr_err("check PPAT failed\n");

This is just extra noise, both individual dest already say check PPAT
failed.

> +
> +	return ret;
> +}
> +
> +static bool value_is_new(struct intel_ppat *ppat, u8 value)

Bit overly generic name for selftests/i915_gem_gtt.c, how about
'ppat_is_new' or 'ppat_find_match_all'.

> +{
> +	int i;
> +
> +	for_each_set_bit(i, ppat->used, ppat->max_entries) {
> +		if (value != ppat->entries[i].value)

		if (value == ppat->entries[i])
			return i;

And drop the braces around for.

	return -ENOENT;

> +static bool value_for_partial_test(struct intel_ppat *ppat, u8 value)

Maybe 'ppat_find_match_ca', similar changes to above function to return
index.

> +{
> +	int i;
> +
> +	if (!value_is_new(ppat, value))
> +		return false;
> +
> +	/*
> +	 * At least, there should be one entry whose cache attribute is
> +	 * same with the required value.
> +	 */

Comment says what the code does, so can be dropped.

> +	for_each_set_bit(i, ppat->used, ppat->max_entries) {
> +		if (GEN8_PPAT_GET_CA(value) !=
> +		    GEN8_PPAT_GET_CA(ppat->entries[i].value))

Again '==' and return true.

> +			continue;
> +
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool value_for_negative_test(struct intel_ppat *ppat, u8 value)

Maybe 'ppat_find_not_match_ca', same with returning index / -ENOENT.

> +{
> +	int i;
> +
> +	if (!value_is_new(ppat, value))
> +		return false;
> +
> +	/*
> +	 * cache attribute has to be different, so i915_ppat_get() would
> +	 * allocate a new entry.
> +	 */

Ditto.

> +	for_each_set_bit(i, ppat->used, ppat->max_entries) {
> +		if (GEN8_PPAT_GET_CA(value) ==
> +		    GEN8_PPAT_GET_CA(ppat->entries[i].value))
> +			return false;

You can drop the braces, here too.

> +	}
> +	return true;
> +}
> +
> +static u8 generate_new_value(struct intel_ppat *ppat,
> +			     bool (*check_value)(struct intel_ppat *, u8))

'ppat_generate_new' or so. Also, by definition of _new, this function
should be doing the check if it already exists, not the filter function
(unnecessary code duplication).

> +{
> +	u8 ca[] = { GEN8_PPAT_WB, GEN8_PPAT_WT, GEN8_PPAT_UC, GEN8_PPAT_WC };
> +	u8 tc[] = { GEN8_PPAT_LLC, GEN8_PPAT_LLCELLC, GEN8_PPAT_LLCeLLC };
> +	u8 age[] = { GEN8_PPAT_AGE(3), GEN8_PPAT_AGE(2), GEN8_PPAT_AGE(1),
> +		     GEN8_PPAT_AGE(0) };
> +	int ca_index, tc_index, age_index;
> +	u8 value;
> +
> +#define for_each_ppat_attr(ca_index, tc_index, age_index) \
> +	for ((ca_index) = 0 ; (ca_index) < ARRAY_SIZE(ca); (ca_index)++) \
> +	for ((tc_index) = 0; (tc_index) < ARRAY_SIZE(tc); (tc_index)++) \
> +	for ((age_index) = 0; (age_index) < ARRAY_SIZE(age); (age_index)++)
> +
> +	for_each_ppat_attr(ca_index, tc_index, age_index) {
> +		value = age[age_index] | ca[ca_index] | tc[tc_index];
> +		if (check_value(ppat, value))
> +			return value;
> +	}
> +#undef for_each_ppat_attr
> +	return 0;

	-ENOSPC?

> +}
> +
> +static const struct intel_ppat_entry *
> +generate_and_check_new_value(struct intel_ppat *ppat)
> +{
> +	struct drm_i915_private *i915 = ppat->i915;
> +	const struct intel_ppat_entry *entry;
> +	u8 value;

Maybe swap these two declarations for extra fine tune.

> +	DECLARE_BITMAP(used, INTEL_MAX_PPAT_ENTRIES);
> +
> +	value = generate_new_value(ppat, value_is_new);
> +	if (!value) {
> +		pr_err("fail to generate new value\n");
> +		return ERR_PTR(-EINVAL);
> +	}

It's better to propagate errors, as much as possible, will be easier
for the next person to extend the tests, possibly adding new error
conditions.

	value = ppat_generate_new(..);
	if (IS_ERR(value)) {
		..
		return ERR_PTR(value);
	}


> +	bitmap_copy(used, ppat->used, ppat->max_entries);
> +
> +	entry = intel_ppat_get(i915, value);
> +	if (IS_ERR(entry)) {
> +		pr_err("fail to get new entry\n");
> +		return ERR_PTR(-EINVAL);
> +	}

Shouldn't the selftest very strictly differentiate between, "unable to
run test" (resources are in use, for example) and "i915 driver failed
the test". Here both problems translate into -EINVAL. Please check the
drm_mm tests for examples.

> +
> +	if (entry->value != value) {
> +		pr_err("value is not expected, expected %x found %x\n",
> +				value, entry->value);
> +		goto err;
> +	}
> +
> +	if (bitmap_equal(used, ppat->used, ppat->max_entries)) {
> +		pr_err("fail to alloc a new entry\n");
> +		goto err;
> +	}

This would be the more logical test to do first, I don't think it gets
triggered otherwise.

> +
> +	return entry;

Newline here.

> +err:
> +	intel_ppat_put(entry);

Usually here too.

> +	return ERR_PTR(-EINVAL);
> +}
> +
> +static int put_and_check_entry(const struct intel_ppat_entry *entry)

add word 'ppat' somewhere.

> +{
> +	intel_ppat_put(entry);
> +
> +	if (entry->value != entry->ppat->clear_value) {
> +		pr_err("fail to put ppat value\n");

Wouldn't 'ppat value was not cleared' be more informative, if
intel_ppat_put returned error, this would be appropriate.

> +		return -EINVAL;
> +	}
> +	return 0;
> +}
> +
> +static int perform_perfect_match_test(struct intel_ppat *ppat)

s/perform/ppat/

> +{
> +	struct drm_i915_private *i915 = ppat->i915;
> +	const struct intel_ppat_entry *entry;
> +	int i;
> +
> +	for_each_set_bit(i, ppat->used, ppat->max_entries) {
> +		entry = intel_ppat_get(i915, ppat->entries[i].value);
> +		if (IS_ERR(entry))
> +			return PTR_ERR(entry);
> +

It's not obvious without looking elsewhere in the code, so it might be
worth mentioning that all entries have been generated to be unique.

		/* All generated PPAT entries are unique */

> +		if (entry != &ppat->entries[i]) {
> +			pr_err("entry is not expected\n");

Do add "goto err" and this becomes.

		if (entry != ..)
			goto err;

Just by glancing at the code, you can see we're doing an extremely 
simple test. Then, without disrupting the code flow, at err: label, you
can add details about what was expected and what we got.


> +			intel_ppat_put(entry);
> +			return -EINVAL;
> +		}
> +		intel_ppat_put(entry);
> +	}
> +	return 0;
> +}
> +
> +static int perform_negative_test(struct intel_ppat *ppat)
> +{
> +	struct drm_i915_private *i915 = ppat->i915;
> +	const struct intel_ppat_entry *entry;
> +	u8 value;
> +
> +	value = generate_new_value(ppat, value_for_negative_test);
> +	if (!value) {
> +		pr_err("fail to generate new value for negative test 2\n");
		
		Drop 'test 2'.

> +		return -EINVAL;
> +	}
> +
> +	entry = intel_ppat_get(i915, value);
> +	if (!IS_ERR(entry) || PTR_ERR(entry) != -ENOSPC) {
> +		pr_err("fail on negative test\n");
> +		return -EINVAL;
> +	}

Again, differentiate "could not run logic" vs. "failure in logic". Lets
try to propagate the error to the topmost function, and we have
possibility to report SKIP vs FAILURE to I-G-T.

> +	return 0;
> +}
> +
> +static int perform_partial_match_test(struct intel_ppat *ppat)
> +{
> +	struct drm_i915_private *i915 = ppat->i915;
> +	const struct intel_ppat_entry *entry;
> +	u8 value;
> +
> +	value = generate_new_value(ppat, value_for_partial_test);

These will read much better when the filter function has meaningful
name.

> +	if (!value) {
> +		pr_err("fail to generate new value for partial test\n");
> +		return -EINVAL;

This would be a 'could not perform test' instead of test failure.

> +	}
> +
> +	entry = intel_ppat_get(i915, value);
> +	if (IS_ERR(entry)) {
> +		pr_err("fail to get new entry\n");
> +		return PTR_ERR(entry);

This would be too a 'don't have space to execute test error', so here
propagating the error would be good idea.

> +	}
> +
> +	if (!(entry->value != value &&
> +	    GEN8_PPAT_GET_CA(entry->value) == GEN8_PPAT_GET_CA(value))) {

Wrong indent, you're negating both, so need to indent to the inner '('

Instead, negating the whole thing allows dropping the fancy indent and
makes the condition much more understandable:
	
	if (entry->value == value || .._CA != _CA

'We should not get equal value, and we should not get different _CA'

That's what partial match is about :)


> +		pr_err("value is not expected, expected %x found %x\n",
> +				value, entry->value);
> +		intel_ppat_put(entry);
> +		return -EINVAL;
> +	}
> +
> +	intel_ppat_put(entry);
> +	return 0;
> +}
> +
> +static int igt_ppat_get(void *arg)
> +{
> +	struct drm_i915_private *i915 = arg;
> +	struct intel_ppat *ppat = &i915->ppat;
> +	const struct intel_ppat_entry **entries, **p;
> +	const struct intel_ppat_entry *entry;
> +	unsigned int size = 0;
> +	int i, ret;
> +
> +	if (!ppat->max_entries)
> +		return 0;
> +
> +	ret = igt_ppat_check(i915);
> +	if (ret)
> +		return ret;
> +
> +	/* case 1: alloc new entries */
> +	entries = NULL;
> +	ret = 0;

This ret = 0 is not needed, we already have preceding
'if (ret) return ret;'

Then drop the extra newline, we're looping to change entries, it should
be close to the beginning of loop.

> +
> +	while (!bitmap_full(ppat->used, ppat->max_entries)) {
> +		p = krealloc(entries, (size + 1) *
> +				   sizeof(struct intel_ppat_entry *),
> +				   GFP_KERNEL);

Weird indent, you can even reduce line length with
(size + 1)*sizeof(*entries). 

> +		if (!p) {
> +			ret = -ENOMEM;
> +			goto ppat_put;
> +		}
> +
> +		entries = p;
> +
> +		p = &entries[size++];
> +		*p = NULL;

I think we could allocate at one go, it's literally a couple of
pointers (ppat->max_entries), I think this is bit much for allocating
pointers.

> +
> +		entry = generate_and_check_new_value(ppat);
> +		if (IS_ERR(entry)) {
> +			ret = PTR_ERR(entry);
> +			pr_err("fail on alloc new entries test\n");
> +			goto ppat_put;
> +		}
> +		*p = entry;
> +	}
> +
> +	/* case 2: perfect match */
> +	ret = perform_perfect_match_test(ppat);
> +	if (ret) {
> +		pr_err("fail on perfect match test\n");
> +		return ret;
> +	}
> +
> +	/* case 3: negative test 1, suppose PPAT table is full now */
> +	ret = perform_negative_test(ppat);
> +	if (ret) {
> +		pr_err("fail on negative test 1\n");
> +		goto ppat_put;
> +	}
> +
> +	/* case 4: partial match */
> +	ret = perform_partial_match_test(ppat);
> +	if (ret) {
> +		pr_err("fail on partial match test\n");
> +		goto ppat_put;
> +	}
> +
> +	/* case 3: negative test 2, suppose PPAT table is still full now */
> +	ret = perform_negative_test(ppat);
> +	if (ret) {
> +		pr_err("fail on negative test 2\n");
> +		goto ppat_put;
> +	}
> +
> +	/* case 5: re-alloc test, make a hole and it should work again */
> +	if (entries) {

entries should not be NULL here, we're skipping function krealloc
fails. So causes extra indent.

> +		for (i = 0; i < size; i++) {
> +			entry = entries[i];
> +
> +			ret = put_and_check_entry(entry);
> +			entries[i] = NULL;
> +			if (ret) {
> +				pr_err("fail on re-alloc test\n");
> +				goto ppat_put;
> +			}
> +
> +			entry = generate_and_check_new_value(ppat);
> +			if (IS_ERR(entry)) {
> +				ret = PTR_ERR(entry);
> +				pr_err("fail on re-alloc test\n");
> +				goto ppat_put;
> +			}
> +			entries[i] = entry;
> +		}
> +	}
> +
> +ppat_put:
> +	if (entries) {

This extra indent can go when we we have goto out; at the allocation
error for entries.

> +		for (i = 0; i < size; i++) {
> +			if (IS_ERR(entries[i]) || !entries[i])

IS_ERR_OR_NULL

> +				continue;
> +
> +			if (ret)
> +				intel_ppat_put(entry);
> +			else
> +				ret = put_and_check_entry(entries[i]);
> +		}
> +		kfree(entries);
> +		entries = NULL;
> +	}

out:

> +	if (ret)
> +		return ret;
> +
> +	return igt_ppat_check(i915);
> +}
> +

With the modifications we should be very much ready with the kselftest,
as I see it.

Regards, Joonas
-- 
Joonas Lahtinen
Open Source Technology Center
Intel Corporation
_______________________________________________
Intel-gfx mailing list
Intel-gfx@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/intel-gfx




[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]
  Powered by Linux