1/*
2 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <errno.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/wait.h>
11#include <unistd.h>
12
13#include <SupportDefs.h>
14
15#include <syscalls.h>
16#include <spinlock_contention.h>
17
18
19#define panic printf
20
21
22struct dummy_spinlock {
23	vint32	lock;
24	vint32	count_low;
25	vint32	count_high;
26};
27
28
29struct dummy_smp_msg {
30	dummy_smp_msg*	next;
31};
32
33
34static int				sNumCPUs = 2;
35static bool				sICIEnabled = true;
36static dummy_spinlock	cpu_msg_spinlock[SMP_MAX_CPUS];
37static dummy_smp_msg*	smp_msgs[SMP_MAX_CPUS];
38static dummy_spinlock	broadcast_msg_spinlock;
39static dummy_smp_msg*	smp_broadcast_msgs;
40
41
42bool
43dummy_are_interrupts_enabled()
44{
45	return false;
46}
47
48
49void
50dummy_acquire_spinlock_nocheck(dummy_spinlock* lock)
51{
52	if (sNumCPUs > 1) {
53		if (dummy_are_interrupts_enabled())
54			panic("acquire_spinlock_nocheck: attempt to acquire lock %p with interrupts enabled\n", lock);
55
56		while (atomic_add(&lock->lock, 1) != 0) {
57		}
58	} else {
59		if (dummy_are_interrupts_enabled())
60			panic("acquire_spinlock_nocheck: attempt to acquire lock %p with interrupts enabled\n", lock);
61		if (atomic_set((int32 *)lock, 1) != 0)
62			panic("acquire_spinlock_nocheck: attempt to acquire lock %p twice on non-SMP system\n", lock);
63	}
64}
65
66
67void
68dummy_release_spinlock(dummy_spinlock* lock)
69{
70	if (sNumCPUs > 1) {
71		if (dummy_are_interrupts_enabled())
72			panic("release_spinlock: attempt to release lock %p with interrupts enabled\n", lock);
73
74		{
75			int32 count = atomic_set(&lock->lock, 0) - 1;
76			if (count < 0) {
77				panic("release_spinlock: lock %p was already released\n", lock);
78			} else {
79				// add to the total count -- deal with carry manually
80				if ((uint32)atomic_add(&lock->count_low, count) + count
81						< (uint32)count) {
82					atomic_add(&lock->count_high, 1);
83				}
84			}
85		}
86	} else {
87		if (dummy_are_interrupts_enabled())
88			panic("release_spinlock: attempt to release lock %p with interrupts enabled\n", lock);
89		if (atomic_set((int32 *)lock, 0) != 1)
90			panic("release_spinlock: lock %p was already released\n", lock);
91	}
92}
93
94
95struct dummy_smp_msg *
96dummy_check_for_message(int currentCPU, int *source_mailbox)
97{
98	struct dummy_smp_msg *msg;
99
100	if (!sICIEnabled)
101		return NULL;
102
103	dummy_acquire_spinlock_nocheck(&cpu_msg_spinlock[currentCPU]);
104	msg = smp_msgs[currentCPU];
105	if (msg != NULL) {
106		printf("yeah\n");
107		dummy_release_spinlock(&cpu_msg_spinlock[currentCPU]);
108		*source_mailbox = 1;
109	} else {
110		// try getting one from the broadcast mailbox
111
112		dummy_release_spinlock(&cpu_msg_spinlock[currentCPU]);
113		dummy_acquire_spinlock_nocheck(&broadcast_msg_spinlock);
114
115		msg = smp_broadcast_msgs;
116		while (msg != NULL) {
117			printf("yeah\n");
118			msg = msg->next;
119		}
120		dummy_release_spinlock(&broadcast_msg_spinlock);
121	}
122	return msg;
123}
124
125
126int32
127dummy_process_pending_ici(int32 currentCPU)
128{
129	int retval = 42;
130	int sourceMailbox = 0;
131
132	dummy_smp_msg* msg = dummy_check_for_message(currentCPU, &sourceMailbox);
133	if (msg == NULL)
134		return retval;
135
136	switch ((addr_t)msg) {
137		case 0:
138			printf("foo\n");
139			break;
140		case 1:
141			printf("foo\n");
142			break;
143		case 2:
144			printf("foo\n");
145			break;
146	}
147
148	return 9;
149}
150
151
152static void
153test_spinlock(dummy_spinlock* lock)
154{
155	while (atomic_add(&lock->lock, -1) != 0)
156		dummy_process_pending_ici(0);
157}
158
159
160static double
161estimate_spinlock_tick_time()
162{
163	// time the spinlock
164	int32 count = (INT_MAX >> 16) + 1;
165	while (true) {
166		bigtime_t startTime = system_time();
167
168		dummy_spinlock lock;
169		lock.lock = count;
170		test_spinlock(&lock);
171
172		bigtime_t totalTime = system_time() - startTime;
173		double tickTime = (double)totalTime / count;
174
175		if (totalTime > 1000000 || INT_MAX >> 2 < count)
176			return tickTime;
177
178		count <<= 1;
179	}
180}
181
182
183static const char*
184time_string(double timeInUsecs, char* buffer)
185{
186	static const char* const kUnits[] = { "us", "ms", "s ", NULL };
187
188	double time = timeInUsecs;
189
190	int32 i = 0;
191	while (time > 1000 && kUnits[i + 1] != NULL) {
192		time /= 1000;
193		i++;
194	}
195
196	sprintf(buffer, "%.3f %s", time, kUnits[i]);
197
198	return buffer;
199}
200
201
202int
203main(int argc, char** argv)
204{
205	// get the initial contention info
206	spinlock_contention_info startInfo;
207	status_t error = _kern_generic_syscall(SPINLOCK_CONTENTION,
208		GET_SPINLOCK_CONTENTION_INFO, &startInfo, sizeof(startInfo));
209	if (error != B_OK) {
210		fprintf(stderr, "Error: Failed to get spinlock contention info: %s\n",
211			strerror(error));
212		exit(1);
213	}
214	bigtime_t startTime = system_time();
215
216	pid_t child = fork();
217	if (child < 0) {
218		fprintf(stderr, "Error: fork() failed: %s\n", strerror(errno));
219		exit(1);
220	}
221
222	if (child == 0) {
223		execvp(argv[1], argv + 1);
224		fprintf(stderr, "Error: exec() failed: %s\n", strerror(errno));
225		exit(1);
226	} else {
227		int status;
228		wait(&status);
229	}
230
231	// get the final contention info
232	spinlock_contention_info endInfo;
233	error = _kern_generic_syscall(SPINLOCK_CONTENTION,
234		GET_SPINLOCK_CONTENTION_INFO, &endInfo, sizeof(endInfo));
235	if (error != B_OK) {
236		fprintf(stderr, "Error: Failed to get spinlock contention info: %s\n",
237			strerror(error));
238		exit(1);
239	}
240	bigtime_t totalTime = system_time() - startTime;
241
242	char buffer[128];
243	printf("\ntotal run time: %s\n", time_string(totalTime, buffer));
244
245	// estimate spinlock tick time
246	printf("estimating time per spinlock tick...\n");
247	double tickTime = estimate_spinlock_tick_time();
248	printf("time per spinlock tick: %s\n",
249		time_string(tickTime, buffer));
250
251	// print results
252	static const char* const kLockNames[] = { "thread creation", NULL };
253	bigtime_t lockCounts[] = {
254		endInfo.thread_creation_spinlock - startInfo.thread_creation_spinlock,
255	};
256
257	printf("\nlock             counter            time   wasted %% CPU\n");
258	printf("-------------------------------------------------------\n");
259	for (int32 i = 0; kLockNames[i] != NULL; i++) {
260		double wastedUsecs = lockCounts[i] * tickTime;
261		printf("%-10s  %12llu  %14s   %12.4f\n", kLockNames[i], lockCounts[i],
262			time_string(wastedUsecs, buffer), wastedUsecs / totalTime * 100);
263	}
264
265	return 0;
266}
267