Improve context switching and scheduler APIs.
[monolithium.git] / kernel / src / lock.c
1 /*
2  * lock.c
3  *
4  * Copyright (C) 2018 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <thread.h>
21
22 #undef lock_t
23 #undef resource_t
24 #include <lock.h>
25
26 #define NO_HOLDER ((uintptr_t)0)
27 #define MULTIPLE_HOLDERS ((uintptr_t)-1)
28 #define UNKNOWN_HOLDER ((uintptr_t)-2)
29 #define TEMPORARY_HOLDER ((uintptr_t)-3)
30
31 void enter_critical(critical_t *critical)
32 {
33     *critical = 0;
34     if (disable_ints()) *critical |= (1 << 0);
35     if (scheduler_enabled) *critical |= (1 << 1);
36
37     disable_ints();
38     scheduler_enabled = FALSE;
39 }
40
41 void leave_critical(critical_t *critical)
42 {
43     if (*critical & (1 << 1)) scheduler_enabled = TRUE;
44     if (*critical & (1 << 0)) enable_ints();
45 }
46
47 void lock_acquire(lock_t *lock)
48 {
49     uintptr_t new_holder = scheduler_enabled ? (uintptr_t)get_current_thread() : UNKNOWN_HOLDER;
50     wait_condition_t condition = { .type = WAIT_UNTIL_EQUAL, .pointer = &lock->holder, .value = 0 };
51
52     for (;;)
53     {
54         uintptr_t old_holder = NO_HOLDER;
55
56         if (__atomic_compare_exchange(&lock->holder, &old_holder, &new_holder, FALSE, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
57         {
58             int32_t new_count = __atomic_add_fetch(&lock->count, 1, __ATOMIC_ACQUIRE);
59             ASSERT(new_count == 1);
60             return;
61         }
62
63         if (scheduler_enabled) scheduler_wait(&condition, NO_TIMEOUT);
64     }
65 }
66
67 static inline void lock_acquire_smart_by(lock_t *lock, uintptr_t new_holder)
68 {
69     wait_condition_t condition1 = { .type = WAIT_UNTIL_EQUAL, .pointer = &lock->holder, .value = 0 };
70     wait_condition_t condition2 = { .type = WAIT_UNTIL_EQUAL, .pointer = &lock->holder, .value = new_holder };
71
72     wait_condition_t *condition = __builtin_alloca(sizeof(wait_condition_t) + 3 * sizeof(wait_condition_t*));
73     condition->type = WAIT_GROUP_ANY;
74     condition->conditions[0] = &condition1;
75     condition->conditions[1] = &condition2;
76     condition->conditions[2] = NULL;
77
78     for (;;)
79     {
80         uintptr_t old_holder = NO_HOLDER;
81         if (__atomic_compare_exchange(&lock->holder, &old_holder, &new_holder, FALSE, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)
82             || old_holder == new_holder)
83         {
84             int32_t new_count = __atomic_add_fetch(&lock->count, 1, __ATOMIC_ACQUIRE);
85             ASSERT(new_count > 0);
86             return;
87         }
88
89         if (scheduler_enabled) scheduler_wait(condition, NO_TIMEOUT);
90     }
91 }
92
93 void lock_acquire_smart(lock_t *lock)
94 {
95     lock_acquire_smart_by(lock, scheduler_enabled ? (uintptr_t)get_current_thread() : UNKNOWN_HOLDER);
96 }
97
98 void lock_acquire_shared(lock_t *lock)
99 {
100     lock_acquire_smart_by(lock, MULTIPLE_HOLDERS);
101 }
102
103 void lock_release(lock_t *lock)
104 {
105     uintptr_t holder;
106
107     for (;;)
108     {
109         holder = __atomic_exchange_n(&lock->holder, TEMPORARY_HOLDER, __ATOMIC_ACQUIRE);
110         if (holder != TEMPORARY_HOLDER) break;
111     }
112
113     ASSERT(holder != NO_HOLDER);
114
115     int32_t new_count = __atomic_sub_fetch(&lock->count, 1, __ATOMIC_RELEASE);
116     ASSERT(new_count >= 0);
117     if (new_count == 0) holder = NO_HOLDER;
118
119     __atomic_store_n(&lock->holder, holder, __ATOMIC_RELEASE);
120     if (scheduler_enabled && holder == NO_HOLDER) syscall_yield_quantum();
121 }