/* * Make sure the value in the test page is read after reading * the expectation for the first time. Pairs with wmb() in * prepare_to_test().
*/
rmb();
val = READ_ONCE(*(u64 *)data->test_pages);
/* * Make sure the value in the test page is read after before * reading the expectation for the second time. Pairs with wmb() * post_test().
*/
rmb();
/* * '0' indicates the sender is between iterations, wait until * the sender is ready for this vCPU to start checking again.
*/ if (!expected) continue;
/* * Re-read the per-vCPU byte to ensure the sender didn't move * onto a new iteration.
*/ if (expected != READ_ONCE(*this_cpu)) continue;
GUEST_ASSERT(val == expected);
}
}
/* * Write per-CPU info indicating what each 'worker' CPU is supposed to see in * test page. '0' means don't check.
*/ staticvoid set_expected_val(void *addr, u64 val, int vcpu_id)
{ void *exp_page = addr + PAGE_SIZE * NTEST_PAGES;
/* * Update PTEs swapping two test pages. * TODO: use swap()/xchg() when these are provided.
*/ staticvoid swap_two_test_pages(vm_paddr_t pte_gva1, vm_paddr_t pte_gva2)
{
uint64_t tmp = *(uint64_t *)pte_gva1;
/* * TODO: replace the silly NOP loop with a proper udelay() implementation.
*/ staticinlinevoid do_delay(void)
{ int i;
for (i = 0; i < 1000000; i++) asmvolatile("nop");
}
/* * Prepare to test: 'disable' workers by setting the expectation to '0', * clear hypercall input page and then swap two test pages.
*/ staticinlinevoid prepare_to_test(struct test_data *data)
{ /* Clear hypercall input page */
memset((void *)data->hcall_gva, 0, PAGE_SIZE);
/* Make sure workers are 'disabled' before we swap PTEs. */
wmb();
/* Make sure workers have enough time to notice */
do_delay();
/* Swap test page mappings */
swap_two_test_pages(data->test_pages_pte[0], data->test_pages_pte[1]);
}
/* * Finalize the test: check hypercall resule set the expected val for * 'worker' CPUs and give them some time to test.
*/ staticinlinevoid post_test(struct test_data *data, u64 exp1, u64 exp2)
{ /* Make sure we change the expectation after swapping PTEs */
wmb();
/* Set the expectation for workers, '0' means don't test */
set_expected_val((void *)data->test_pages, exp1, WORKER_VCPU_ID_1);
set_expected_val((void *)data->test_pages, exp2, WORKER_VCPU_ID_2);
/* Make sure workers have enough time to test */
do_delay();
}
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE for WORKER_VCPU_ID_1 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush->processor_mask = BIT(WORKER_VCPU_ID_1);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE, hcall_gpa,
hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2, 0x0);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST for WORKER_VCPU_ID_1 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush->processor_mask = BIT(WORKER_VCPU_ID_1);
flush->gva_list[0] = (u64)data->test_pages;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2, 0x0);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE for HV_FLUSH_ALL_PROCESSORS */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES |
HV_FLUSH_ALL_PROCESSORS;
flush->processor_mask = 0;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE, hcall_gpa,
hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2, i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST for HV_FLUSH_ALL_PROCESSORS */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES |
HV_FLUSH_ALL_PROCESSORS;
flush->gva_list[0] = (u64)data->test_pages;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX for WORKER_VCPU_ID_2 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_2 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_2 % 64);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX |
(1 << HV_HYPERCALL_VARHEAD_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, 0x0, i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX for WORKER_VCPU_ID_2 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_2 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_2 % 64); /* bank_contents and gva_list occupy the same space, thus [1] */
flush_ex->gva_list[1] = (u64)data->test_pages;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX |
(1 << HV_HYPERCALL_VARHEAD_OFFSET) |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, 0x0, i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX for both vCPUs */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_2 / 64) |
BIT_ULL(WORKER_VCPU_ID_1 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_1 % 64);
flush_ex->hv_vp_set.bank_contents[1] = BIT_ULL(WORKER_VCPU_ID_2 % 64);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX |
(2 << HV_HYPERCALL_VARHEAD_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX for both vCPUs */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_1 / 64) |
BIT_ULL(WORKER_VCPU_ID_2 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_1 % 64);
flush_ex->hv_vp_set.bank_contents[1] = BIT_ULL(WORKER_VCPU_ID_2 % 64); /* bank_contents and gva_list occupy the same space, thus [2] */
flush_ex->gva_list[2] = (u64)data->test_pages;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX |
(2 << HV_HYPERCALL_VARHEAD_OFFSET) |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX for HV_GENERIC_SET_ALL */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_ALL;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX,
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX for HV_GENERIC_SET_ALL */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_ALL;
flush_ex->gva_list[0] = (u64)data->test_pages;
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
hcall_gpa, hcall_gpa + PAGE_SIZE);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
/* "Fast" hypercalls */
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE for WORKER_VCPU_ID_1 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->processor_mask = BIT(WORKER_VCPU_ID_1);
hyperv_write_xmm_input(&flush->processor_mask, 1);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE |
HV_HYPERCALL_FAST_BIT, 0x0,
HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2, 0x0);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST for WORKER_VCPU_ID_1 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->processor_mask = BIT(WORKER_VCPU_ID_1);
flush->gva_list[0] = (u64)data->test_pages;
hyperv_write_xmm_input(&flush->processor_mask, 1);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST |
HV_HYPERCALL_FAST_BIT |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2, 0x0);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE for HV_FLUSH_ALL_PROCESSORS */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
hyperv_write_xmm_input(&flush->processor_mask, 1);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE |
HV_HYPERCALL_FAST_BIT, 0x0,
HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES |
HV_FLUSH_ALL_PROCESSORS);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST for HV_FLUSH_ALL_PROCESSORS */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush->gva_list[0] = (u64)data->test_pages;
hyperv_write_xmm_input(&flush->processor_mask, 1);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST |
HV_HYPERCALL_FAST_BIT |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET), 0x0,
HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES |
HV_FLUSH_ALL_PROCESSORS);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX for WORKER_VCPU_ID_2 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_2 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_2 % 64);
hyperv_write_xmm_input(&flush_ex->hv_vp_set, 2);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX |
HV_HYPERCALL_FAST_BIT |
(1 << HV_HYPERCALL_VARHEAD_OFFSET),
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, 0x0, i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX for WORKER_VCPU_ID_2 */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_2 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_2 % 64); /* bank_contents and gva_list occupy the same space, thus [1] */
flush_ex->gva_list[1] = (u64)data->test_pages;
hyperv_write_xmm_input(&flush_ex->hv_vp_set, 2);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX |
HV_HYPERCALL_FAST_BIT |
(1 << HV_HYPERCALL_VARHEAD_OFFSET) |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, 0x0, i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX for both vCPUs */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_2 / 64) |
BIT_ULL(WORKER_VCPU_ID_1 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_1 % 64);
flush_ex->hv_vp_set.bank_contents[1] = BIT_ULL(WORKER_VCPU_ID_2 % 64);
hyperv_write_xmm_input(&flush_ex->hv_vp_set, 2);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX |
HV_HYPERCALL_FAST_BIT |
(2 << HV_HYPERCALL_VARHEAD_OFFSET),
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, i % 2 ? TESTVAL1 :
TESTVAL2, i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX for both vCPUs */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->hv_vp_set.format = HV_GENERIC_SET_SPARSE_4K;
flush_ex->hv_vp_set.valid_bank_mask = BIT_ULL(WORKER_VCPU_ID_1 / 64) |
BIT_ULL(WORKER_VCPU_ID_2 / 64);
flush_ex->hv_vp_set.bank_contents[0] = BIT_ULL(WORKER_VCPU_ID_1 % 64);
flush_ex->hv_vp_set.bank_contents[1] = BIT_ULL(WORKER_VCPU_ID_2 % 64); /* bank_contents and gva_list occupy the same space, thus [2] */
flush_ex->gva_list[2] = (u64)data->test_pages;
hyperv_write_xmm_input(&flush_ex->hv_vp_set, 3);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX |
HV_HYPERCALL_FAST_BIT |
(2 << HV_HYPERCALL_VARHEAD_OFFSET) |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX for HV_GENERIC_SET_ALL */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_ALL;
hyperv_write_xmm_input(&flush_ex->hv_vp_set, 2);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX |
HV_HYPERCALL_FAST_BIT,
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_SYNC(stage++);
/* HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX for HV_GENERIC_SET_ALL */ for (i = 0; i < NTRY; i++) {
prepare_to_test(data);
flush_ex->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
flush_ex->hv_vp_set.format = HV_GENERIC_SET_ALL;
flush_ex->gva_list[0] = (u64)data->test_pages;
hyperv_write_xmm_input(&flush_ex->hv_vp_set, 2);
hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX |
HV_HYPERCALL_FAST_BIT |
(1UL << HV_HYPERCALL_REP_COMP_OFFSET),
0x0, HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES);
post_test(data, i % 2 ? TESTVAL1 : TESTVAL2,
i % 2 ? TESTVAL1 : TESTVAL2);
}
GUEST_DONE();
}
staticvoid *vcpu_thread(void *arg)
{ struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg; struct ucall uc; int old; int r;
r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);
TEST_ASSERT(!r, "pthread_setcanceltype failed on vcpu_id=%u with errno=%d",
vcpu->id, r);
/* * Test pages: the first one is filled with '0x01's, the second with '0x02's * and the test will swap their mappings. The third page keeps the indication * about the current state of mappings.
*/
data->test_pages = vm_vaddr_alloc_pages(vm, NTEST_PAGES + 1); for (i = 0; i < NTEST_PAGES; i++)
memset(addr_gva2hva(vm, data->test_pages + PAGE_SIZE * i),
(u8)(i + 1), PAGE_SIZE);
set_expected_val(addr_gva2hva(vm, data->test_pages), 0x0, WORKER_VCPU_ID_1);
set_expected_val(addr_gva2hva(vm, data->test_pages), 0x0, WORKER_VCPU_ID_2);
/* * Get PTE pointers for test pages and map them inside the guest. * Use separate page for each PTE for simplicity.
*/
gva = vm_vaddr_unused_gap(vm, NTEST_PAGES * PAGE_SIZE, KVM_UTIL_MIN_VADDR); for (i = 0; i < NTEST_PAGES; i++) {
pte = vm_get_page_table_entry(vm, data->test_pages + i * PAGE_SIZE);
gpa = addr_hva2gpa(vm, pte);
__virt_pg_map(vm, gva + PAGE_SIZE * i, gpa & PAGE_MASK, PG_LEVEL_4K);
data->test_pages_pte[i] = gva + (gpa & ~PAGE_MASK);
}
/* * Sender vCPU which performs the test: swaps test pages, sets expectation * for 'workers' and issues TLB flush hypercalls.
*/
vcpu_args_set(vcpu[0], 1, test_data_page);
vcpu_set_hv_cpuid(vcpu[0]);
/* Create worker vCPUs which check the contents of the test pages */
vcpu[1] = vm_vcpu_add(vm, WORKER_VCPU_ID_1, worker_guest_code);
vcpu_args_set(vcpu[1], 1, test_data_page);
vcpu_set_msr(vcpu[1], HV_X64_MSR_VP_INDEX, WORKER_VCPU_ID_1);
vcpu_set_hv_cpuid(vcpu[1]);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.