xref: /openbmc/linux/tools/testing/selftests/kvm/steal_time.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
194c4b76bSAndrew Jones // SPDX-License-Identifier: GPL-2.0
294c4b76bSAndrew Jones /*
394c4b76bSAndrew Jones  * steal/stolen time test
494c4b76bSAndrew Jones  *
594c4b76bSAndrew Jones  * Copyright (C) 2020, Red Hat, Inc.
694c4b76bSAndrew Jones  */
794c4b76bSAndrew Jones #define _GNU_SOURCE
894c4b76bSAndrew Jones #include <stdio.h>
994c4b76bSAndrew Jones #include <time.h>
1094c4b76bSAndrew Jones #include <sched.h>
1194c4b76bSAndrew Jones #include <pthread.h>
1294c4b76bSAndrew Jones #include <linux/kernel.h>
1394c4b76bSAndrew Jones #include <asm/kvm.h>
1494c4b76bSAndrew Jones #include <asm/kvm_para.h>
1594c4b76bSAndrew Jones 
1694c4b76bSAndrew Jones #include "test_util.h"
1794c4b76bSAndrew Jones #include "kvm_util.h"
1894c4b76bSAndrew Jones #include "processor.h"
1994c4b76bSAndrew Jones 
2094c4b76bSAndrew Jones #define NR_VCPUS		4
2194c4b76bSAndrew Jones #define ST_GPA_BASE		(1 << 30)
2294c4b76bSAndrew Jones 
2394c4b76bSAndrew Jones static void *st_gva[NR_VCPUS];
2494c4b76bSAndrew Jones static uint64_t guest_stolen_time[NR_VCPUS];
2594c4b76bSAndrew Jones 
2694c4b76bSAndrew Jones #if defined(__x86_64__)
2794c4b76bSAndrew Jones 
2894c4b76bSAndrew Jones /* steal_time must have 64-byte alignment */
2994c4b76bSAndrew Jones #define STEAL_TIME_SIZE		((sizeof(struct kvm_steal_time) + 63) & ~63)
3094c4b76bSAndrew Jones 
check_status(struct kvm_steal_time * st)3194c4b76bSAndrew Jones static void check_status(struct kvm_steal_time *st)
3294c4b76bSAndrew Jones {
3394c4b76bSAndrew Jones 	GUEST_ASSERT(!(READ_ONCE(st->version) & 1));
34*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(READ_ONCE(st->flags), 0);
35*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(READ_ONCE(st->preempted), 0);
3694c4b76bSAndrew Jones }
3794c4b76bSAndrew Jones 
guest_code(int cpu)3894c4b76bSAndrew Jones static void guest_code(int cpu)
3994c4b76bSAndrew Jones {
4094c4b76bSAndrew Jones 	struct kvm_steal_time *st = st_gva[cpu];
4194c4b76bSAndrew Jones 	uint32_t version;
4294c4b76bSAndrew Jones 
43*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(rdmsr(MSR_KVM_STEAL_TIME), ((uint64_t)st_gva[cpu] | KVM_MSR_ENABLED));
4494c4b76bSAndrew Jones 
4594c4b76bSAndrew Jones 	memset(st, 0, sizeof(*st));
4694c4b76bSAndrew Jones 	GUEST_SYNC(0);
4794c4b76bSAndrew Jones 
4894c4b76bSAndrew Jones 	check_status(st);
4994c4b76bSAndrew Jones 	WRITE_ONCE(guest_stolen_time[cpu], st->steal);
5094c4b76bSAndrew Jones 	version = READ_ONCE(st->version);
5194c4b76bSAndrew Jones 	check_status(st);
5294c4b76bSAndrew Jones 	GUEST_SYNC(1);
5394c4b76bSAndrew Jones 
5494c4b76bSAndrew Jones 	check_status(st);
5594c4b76bSAndrew Jones 	GUEST_ASSERT(version < READ_ONCE(st->version));
5694c4b76bSAndrew Jones 	WRITE_ONCE(guest_stolen_time[cpu], st->steal);
5794c4b76bSAndrew Jones 	check_status(st);
5894c4b76bSAndrew Jones 	GUEST_DONE();
5994c4b76bSAndrew Jones }
6094c4b76bSAndrew Jones 
is_steal_time_supported(struct kvm_vcpu * vcpu)6199801604SSean Christopherson static bool is_steal_time_supported(struct kvm_vcpu *vcpu)
6294c4b76bSAndrew Jones {
63601c067fSSean Christopherson 	return kvm_cpu_has(X86_FEATURE_KVM_STEAL_TIME);
6494c4b76bSAndrew Jones }
6594c4b76bSAndrew Jones 
steal_time_init(struct kvm_vcpu * vcpu,uint32_t i)6699801604SSean Christopherson static void steal_time_init(struct kvm_vcpu *vcpu, uint32_t i)
6799801604SSean Christopherson {
6894c4b76bSAndrew Jones 	int ret;
6994c4b76bSAndrew Jones 
7094c4b76bSAndrew Jones 	/* ST_GPA_BASE is identity mapped */
7194c4b76bSAndrew Jones 	st_gva[i] = (void *)(ST_GPA_BASE + i * STEAL_TIME_SIZE);
7299801604SSean Christopherson 	sync_global_to_guest(vcpu->vm, st_gva[i]);
7394c4b76bSAndrew Jones 
74768e9a61SSean Christopherson 	ret = _vcpu_set_msr(vcpu, MSR_KVM_STEAL_TIME,
7599801604SSean Christopherson 			    (ulong)st_gva[i] | KVM_STEAL_RESERVED_MASK);
7694c4b76bSAndrew Jones 	TEST_ASSERT(ret == 0, "Bad GPA didn't fail");
7794c4b76bSAndrew Jones 
78768e9a61SSean Christopherson 	vcpu_set_msr(vcpu, MSR_KVM_STEAL_TIME, (ulong)st_gva[i] | KVM_MSR_ENABLED);
7994c4b76bSAndrew Jones }
8094c4b76bSAndrew Jones 
steal_time_dump(struct kvm_vm * vm,uint32_t vcpu_idx)8199801604SSean Christopherson static void steal_time_dump(struct kvm_vm *vm, uint32_t vcpu_idx)
8294c4b76bSAndrew Jones {
8399801604SSean Christopherson 	struct kvm_steal_time *st = addr_gva2hva(vm, (ulong)st_gva[vcpu_idx]);
8494c4b76bSAndrew Jones 	int i;
8594c4b76bSAndrew Jones 
8699801604SSean Christopherson 	pr_info("VCPU%d:\n", vcpu_idx);
8794c4b76bSAndrew Jones 	pr_info("    steal:     %lld\n", st->steal);
8894c4b76bSAndrew Jones 	pr_info("    version:   %d\n", st->version);
8994c4b76bSAndrew Jones 	pr_info("    flags:     %d\n", st->flags);
9094c4b76bSAndrew Jones 	pr_info("    preempted: %d\n", st->preempted);
9194c4b76bSAndrew Jones 	pr_info("    u8_pad:    ");
9294c4b76bSAndrew Jones 	for (i = 0; i < 3; ++i)
9394c4b76bSAndrew Jones 		pr_info("%d", st->u8_pad[i]);
9494c4b76bSAndrew Jones 	pr_info("\n    pad:       ");
9594c4b76bSAndrew Jones 	for (i = 0; i < 11; ++i)
9694c4b76bSAndrew Jones 		pr_info("%d", st->pad[i]);
9794c4b76bSAndrew Jones 	pr_info("\n");
9894c4b76bSAndrew Jones }
9994c4b76bSAndrew Jones 
10094c4b76bSAndrew Jones #elif defined(__aarch64__)
10194c4b76bSAndrew Jones 
10294c4b76bSAndrew Jones /* PV_TIME_ST must have 64-byte alignment */
10394c4b76bSAndrew Jones #define STEAL_TIME_SIZE		((sizeof(struct st_time) + 63) & ~63)
10494c4b76bSAndrew Jones 
10594c4b76bSAndrew Jones #define SMCCC_ARCH_FEATURES	0x80000001
10694c4b76bSAndrew Jones #define PV_TIME_FEATURES	0xc5000020
10794c4b76bSAndrew Jones #define PV_TIME_ST		0xc5000021
10894c4b76bSAndrew Jones 
10994c4b76bSAndrew Jones struct st_time {
11094c4b76bSAndrew Jones 	uint32_t rev;
11194c4b76bSAndrew Jones 	uint32_t attr;
11294c4b76bSAndrew Jones 	uint64_t st_time;
11394c4b76bSAndrew Jones };
11494c4b76bSAndrew Jones 
smccc(uint32_t func,uint64_t arg)11501f91acbSOliver Upton static int64_t smccc(uint32_t func, uint64_t arg)
11694c4b76bSAndrew Jones {
117e918e2bcSOliver Upton 	struct arm_smccc_res res;
11894c4b76bSAndrew Jones 
119e918e2bcSOliver Upton 	smccc_hvc(func, arg, 0, 0, 0, 0, 0, 0, &res);
120e918e2bcSOliver Upton 	return res.a0;
12194c4b76bSAndrew Jones }
12294c4b76bSAndrew Jones 
check_status(struct st_time * st)12394c4b76bSAndrew Jones static void check_status(struct st_time *st)
12494c4b76bSAndrew Jones {
125*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(READ_ONCE(st->rev), 0);
126*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(READ_ONCE(st->attr), 0);
12794c4b76bSAndrew Jones }
12894c4b76bSAndrew Jones 
guest_code(int cpu)12994c4b76bSAndrew Jones static void guest_code(int cpu)
13094c4b76bSAndrew Jones {
13194c4b76bSAndrew Jones 	struct st_time *st;
13294c4b76bSAndrew Jones 	int64_t status;
13394c4b76bSAndrew Jones 
13494c4b76bSAndrew Jones 	status = smccc(SMCCC_ARCH_FEATURES, PV_TIME_FEATURES);
135*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(status, 0);
13694c4b76bSAndrew Jones 	status = smccc(PV_TIME_FEATURES, PV_TIME_FEATURES);
137*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(status, 0);
13894c4b76bSAndrew Jones 	status = smccc(PV_TIME_FEATURES, PV_TIME_ST);
139*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(status, 0);
14094c4b76bSAndrew Jones 
14194c4b76bSAndrew Jones 	status = smccc(PV_TIME_ST, 0);
142*3d9bd831SSean Christopherson 	GUEST_ASSERT_NE(status, -1);
143*3d9bd831SSean Christopherson 	GUEST_ASSERT_EQ(status, (ulong)st_gva[cpu]);
14494c4b76bSAndrew Jones 
14594c4b76bSAndrew Jones 	st = (struct st_time *)status;
14694c4b76bSAndrew Jones 	GUEST_SYNC(0);
14794c4b76bSAndrew Jones 
14894c4b76bSAndrew Jones 	check_status(st);
14994c4b76bSAndrew Jones 	WRITE_ONCE(guest_stolen_time[cpu], st->st_time);
15094c4b76bSAndrew Jones 	GUEST_SYNC(1);
15194c4b76bSAndrew Jones 
15294c4b76bSAndrew Jones 	check_status(st);
15394c4b76bSAndrew Jones 	WRITE_ONCE(guest_stolen_time[cpu], st->st_time);
15494c4b76bSAndrew Jones 	GUEST_DONE();
15594c4b76bSAndrew Jones }
15694c4b76bSAndrew Jones 
is_steal_time_supported(struct kvm_vcpu * vcpu)15799801604SSean Christopherson static bool is_steal_time_supported(struct kvm_vcpu *vcpu)
15894c4b76bSAndrew Jones {
15994c4b76bSAndrew Jones 	struct kvm_device_attr dev = {
16094c4b76bSAndrew Jones 		.group = KVM_ARM_VCPU_PVTIME_CTRL,
16194c4b76bSAndrew Jones 		.attr = KVM_ARM_VCPU_PVTIME_IPA,
16294c4b76bSAndrew Jones 	};
16394c4b76bSAndrew Jones 
164768e9a61SSean Christopherson 	return !__vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev);
16594c4b76bSAndrew Jones }
16694c4b76bSAndrew Jones 
steal_time_init(struct kvm_vcpu * vcpu,uint32_t i)16799801604SSean Christopherson static void steal_time_init(struct kvm_vcpu *vcpu, uint32_t i)
16899801604SSean Christopherson {
16999801604SSean Christopherson 	struct kvm_vm *vm = vcpu->vm;
17094c4b76bSAndrew Jones 	uint64_t st_ipa;
17199801604SSean Christopherson 	int ret;
17294c4b76bSAndrew Jones 
17399801604SSean Christopherson 	struct kvm_device_attr dev = {
17499801604SSean Christopherson 		.group = KVM_ARM_VCPU_PVTIME_CTRL,
17599801604SSean Christopherson 		.attr = KVM_ARM_VCPU_PVTIME_IPA,
17699801604SSean Christopherson 		.addr = (uint64_t)&st_ipa,
17799801604SSean Christopherson 	};
17894c4b76bSAndrew Jones 
179768e9a61SSean Christopherson 	vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev);
18094c4b76bSAndrew Jones 
18194c4b76bSAndrew Jones 	/* ST_GPA_BASE is identity mapped */
18294c4b76bSAndrew Jones 	st_gva[i] = (void *)(ST_GPA_BASE + i * STEAL_TIME_SIZE);
18394c4b76bSAndrew Jones 	sync_global_to_guest(vm, st_gva[i]);
18494c4b76bSAndrew Jones 
18594c4b76bSAndrew Jones 	st_ipa = (ulong)st_gva[i] | 1;
186768e9a61SSean Christopherson 	ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev);
18794c4b76bSAndrew Jones 	TEST_ASSERT(ret == -1 && errno == EINVAL, "Bad IPA didn't report EINVAL");
18894c4b76bSAndrew Jones 
18994c4b76bSAndrew Jones 	st_ipa = (ulong)st_gva[i];
190768e9a61SSean Christopherson 	vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev);
19194c4b76bSAndrew Jones 
192768e9a61SSean Christopherson 	ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev);
19394c4b76bSAndrew Jones 	TEST_ASSERT(ret == -1 && errno == EEXIST, "Set IPA twice without EEXIST");
19494c4b76bSAndrew Jones }
19594c4b76bSAndrew Jones 
steal_time_dump(struct kvm_vm * vm,uint32_t vcpu_idx)19699801604SSean Christopherson static void steal_time_dump(struct kvm_vm *vm, uint32_t vcpu_idx)
19794c4b76bSAndrew Jones {
19899801604SSean Christopherson 	struct st_time *st = addr_gva2hva(vm, (ulong)st_gva[vcpu_idx]);
19994c4b76bSAndrew Jones 
20099801604SSean Christopherson 	pr_info("VCPU%d:\n", vcpu_idx);
20194c4b76bSAndrew Jones 	pr_info("    rev:     %d\n", st->rev);
20294c4b76bSAndrew Jones 	pr_info("    attr:    %d\n", st->attr);
20394c4b76bSAndrew Jones 	pr_info("    st_time: %ld\n", st->st_time);
20494c4b76bSAndrew Jones }
20594c4b76bSAndrew Jones 
20694c4b76bSAndrew Jones #endif
20794c4b76bSAndrew Jones 
do_steal_time(void * arg)20894c4b76bSAndrew Jones static void *do_steal_time(void *arg)
20994c4b76bSAndrew Jones {
21094c4b76bSAndrew Jones 	struct timespec ts, stop;
21194c4b76bSAndrew Jones 
21294c4b76bSAndrew Jones 	clock_gettime(CLOCK_MONOTONIC, &ts);
21394c4b76bSAndrew Jones 	stop = timespec_add_ns(ts, MIN_RUN_DELAY_NS);
21494c4b76bSAndrew Jones 
21594c4b76bSAndrew Jones 	while (1) {
21694c4b76bSAndrew Jones 		clock_gettime(CLOCK_MONOTONIC, &ts);
217bfcaa849SAndrew Jones 		if (timespec_to_ns(timespec_sub(ts, stop)) >= 0)
21894c4b76bSAndrew Jones 			break;
21994c4b76bSAndrew Jones 	}
22094c4b76bSAndrew Jones 
22194c4b76bSAndrew Jones 	return NULL;
22294c4b76bSAndrew Jones }
22394c4b76bSAndrew Jones 
run_vcpu(struct kvm_vcpu * vcpu)22499801604SSean Christopherson static void run_vcpu(struct kvm_vcpu *vcpu)
22594c4b76bSAndrew Jones {
22694c4b76bSAndrew Jones 	struct ucall uc;
22794c4b76bSAndrew Jones 
228768e9a61SSean Christopherson 	vcpu_run(vcpu);
22994c4b76bSAndrew Jones 
230768e9a61SSean Christopherson 	switch (get_ucall(vcpu, &uc)) {
23194c4b76bSAndrew Jones 	case UCALL_SYNC:
23294c4b76bSAndrew Jones 	case UCALL_DONE:
23394c4b76bSAndrew Jones 		break;
23494c4b76bSAndrew Jones 	case UCALL_ABORT:
235594a1c27SColton Lewis 		REPORT_GUEST_ASSERT(uc);
23694c4b76bSAndrew Jones 	default:
23794c4b76bSAndrew Jones 		TEST_ASSERT(false, "Unexpected exit: %s",
23899801604SSean Christopherson 			    exit_reason_str(vcpu->run->exit_reason));
23994c4b76bSAndrew Jones 	}
24094c4b76bSAndrew Jones }
24194c4b76bSAndrew Jones 
main(int ac,char ** av)24294c4b76bSAndrew Jones int main(int ac, char **av)
24394c4b76bSAndrew Jones {
24499801604SSean Christopherson 	struct kvm_vcpu *vcpus[NR_VCPUS];
24594c4b76bSAndrew Jones 	struct kvm_vm *vm;
24694c4b76bSAndrew Jones 	pthread_attr_t attr;
24794c4b76bSAndrew Jones 	pthread_t thread;
24894c4b76bSAndrew Jones 	cpu_set_t cpuset;
24994c4b76bSAndrew Jones 	unsigned int gpages;
25094c4b76bSAndrew Jones 	long stolen_time;
25194c4b76bSAndrew Jones 	long run_delay;
25294c4b76bSAndrew Jones 	bool verbose;
25394c4b76bSAndrew Jones 	int i;
25494c4b76bSAndrew Jones 
25594c4b76bSAndrew Jones 	verbose = ac > 1 && (!strncmp(av[1], "-v", 3) || !strncmp(av[1], "--verbose", 10));
25694c4b76bSAndrew Jones 
25794c4b76bSAndrew Jones 	/* Set CPU affinity so we can force preemption of the VCPU */
25894c4b76bSAndrew Jones 	CPU_ZERO(&cpuset);
25994c4b76bSAndrew Jones 	CPU_SET(0, &cpuset);
26094c4b76bSAndrew Jones 	pthread_attr_init(&attr);
26194c4b76bSAndrew Jones 	pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
26294c4b76bSAndrew Jones 	pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
26394c4b76bSAndrew Jones 
26499801604SSean Christopherson 	/* Create a VM and an identity mapped memslot for the steal time structure */
26599801604SSean Christopherson 	vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
26694c4b76bSAndrew Jones 	gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * NR_VCPUS);
26794c4b76bSAndrew Jones 	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0);
2684307af73SSean Christopherson 	virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages);
26994c4b76bSAndrew Jones 
2707ed397d1SSean Christopherson 	TEST_REQUIRE(is_steal_time_supported(vcpus[0]));
27194c4b76bSAndrew Jones 
27294c4b76bSAndrew Jones 	/* Run test on each VCPU */
27394c4b76bSAndrew Jones 	for (i = 0; i < NR_VCPUS; ++i) {
27499801604SSean Christopherson 		steal_time_init(vcpus[i], i);
27599801604SSean Christopherson 
276768e9a61SSean Christopherson 		vcpu_args_set(vcpus[i], 1, i);
27799801604SSean Christopherson 
27894c4b76bSAndrew Jones 		/* First VCPU run initializes steal-time */
27999801604SSean Christopherson 		run_vcpu(vcpus[i]);
28094c4b76bSAndrew Jones 
28194c4b76bSAndrew Jones 		/* Second VCPU run, expect guest stolen time to be <= run_delay */
28299801604SSean Christopherson 		run_vcpu(vcpus[i]);
28394c4b76bSAndrew Jones 		sync_global_from_guest(vm, guest_stolen_time[i]);
28494c4b76bSAndrew Jones 		stolen_time = guest_stolen_time[i];
28594c4b76bSAndrew Jones 		run_delay = get_run_delay();
28694c4b76bSAndrew Jones 		TEST_ASSERT(stolen_time <= run_delay,
28794c4b76bSAndrew Jones 			    "Expected stolen time <= %ld, got %ld",
28894c4b76bSAndrew Jones 			    run_delay, stolen_time);
28994c4b76bSAndrew Jones 
29094c4b76bSAndrew Jones 		/* Steal time from the VCPU. The steal time thread has the same CPU affinity as the VCPUs. */
29194c4b76bSAndrew Jones 		run_delay = get_run_delay();
29294c4b76bSAndrew Jones 		pthread_create(&thread, &attr, do_steal_time, NULL);
29394c4b76bSAndrew Jones 		do
294bac0b135SAndrew Jones 			sched_yield();
29594c4b76bSAndrew Jones 		while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS);
29694c4b76bSAndrew Jones 		pthread_join(thread, NULL);
29794c4b76bSAndrew Jones 		run_delay = get_run_delay() - run_delay;
29894c4b76bSAndrew Jones 		TEST_ASSERT(run_delay >= MIN_RUN_DELAY_NS,
29994c4b76bSAndrew Jones 			    "Expected run_delay >= %ld, got %ld",
30094c4b76bSAndrew Jones 			    MIN_RUN_DELAY_NS, run_delay);
30194c4b76bSAndrew Jones 
30294c4b76bSAndrew Jones 		/* Run VCPU again to confirm stolen time is consistent with run_delay */
30399801604SSean Christopherson 		run_vcpu(vcpus[i]);
30494c4b76bSAndrew Jones 		sync_global_from_guest(vm, guest_stolen_time[i]);
30594c4b76bSAndrew Jones 		stolen_time = guest_stolen_time[i] - stolen_time;
30694c4b76bSAndrew Jones 		TEST_ASSERT(stolen_time >= run_delay,
30794c4b76bSAndrew Jones 			    "Expected stolen time >= %ld, got %ld",
30894c4b76bSAndrew Jones 			    run_delay, stolen_time);
30994c4b76bSAndrew Jones 
31094c4b76bSAndrew Jones 		if (verbose) {
31194c4b76bSAndrew Jones 			pr_info("VCPU%d: total-stolen-time=%ld test-stolen-time=%ld", i,
31294c4b76bSAndrew Jones 				guest_stolen_time[i], stolen_time);
31394c4b76bSAndrew Jones 			if (stolen_time == run_delay)
31494c4b76bSAndrew Jones 				pr_info(" (BONUS: guest test-stolen-time even exactly matches test-run_delay)");
31594c4b76bSAndrew Jones 			pr_info("\n");
31694c4b76bSAndrew Jones 			steal_time_dump(vm, i);
31794c4b76bSAndrew Jones 		}
31894c4b76bSAndrew Jones 	}
31994c4b76bSAndrew Jones 
32094c4b76bSAndrew Jones 	return 0;
32194c4b76bSAndrew Jones }
322