source: mainline/kernel/generic/src/cap/cap.c@ eed4139

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since eed4139 was eadaeae8, checked in by Jakub Jermar <jakub@…>, 8 years ago

Make capability handles type-safe

Define distinct pointer types for the handles of the supported
capability types and use them instead of integer handles. This makes it
virtually impossible to pass a non-handle or a handle of different type
instead of the proper handle. Also turn cap_handle_t into an "untyped"
capability handle that can be assigned to and from the "typed" handles.

This commit also fixes a bug in msim-con driver, which wrongly used the
IRQ number instead of the IRQ capability handle to unregister the IRQ.

This commit also fixes the wrong use of the capability handle instead
of error code in libusbhost.

  • Property mode set to 100644
File size: 12.5 KB
Line 
1/*
2 * Copyright (c) 2017 Jakub Jermar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup generic
30 * @{
31 */
32/** @file
33 */
34
35/*
36 * HelenOS capabilities are task-local names for references to kernel objects.
37 * Kernel objects are reference-counted wrappers for a select group of objects
38 * allocated in and by the kernel that can be made accessible to userspace in a
39 * controlled way via integer handles.
40 *
41 * A kernel object (kobject_t) encapsulates one of the following raw objects:
42 *
43 * - IPC call
44 * - IPC phone
45 * - IRQ object
46 *
47 * A capability (cap_t) is either free, allocated or published. Free
48 * capabilities can be allocated, which reserves the capability handle in the
49 * task-local capability space. Allocated capabilities can be published, which
50 * associates them with an existing kernel object. Userspace can only access
51 * published capabilities.
52 *
53 * A published capability may get unpublished, which disassociates it from the
54 * underlying kernel object and puts it back into the allocated state. An
55 * allocated capability can be freed to become available for future use.
56 *
57 * There is a 1:1 correspondence between a kernel object (kobject_t) and the
58 * actual raw object it encapsulates. A kernel object (kobject_t) may have
59 * multiple references, either implicit from one or more capabilities (cap_t),
60 * even from capabilities in different tasks, or explicit as a result of
61 * creating a new reference from a capability handle using kobject_get(), or
62 * creating a new reference from an already existing reference by
63 * kobject_add_ref() or as a result of unpublishing a capability and
64 * disassociating it from its kobject_t using cap_unpublish().
65 *
66 * As kernel objects are reference-counted, they get automatically destroyed
67 * when their last reference is dropped in kobject_put(). The idea is that
68 * whenever a kernel object is inserted into some sort of a container (e.g. a
69 * list or hash table), its reference count should be incremented via
70 * kobject_get() or kobject_add_ref(). When the kernel object is removed from
71 * the container, the reference count should go down via a call to
72 * kobject_put().
73 */
74
75#include <cap/cap.h>
76#include <abi/cap.h>
77#include <proc/task.h>
78#include <synch/mutex.h>
79#include <abi/errno.h>
80#include <mm/slab.h>
81#include <adt/list.h>
82
83#include <stdint.h>
84
85#define CAPS_START (CAP_NIL + 1)
86#define CAPS_SIZE (INT_MAX - CAPS_START)
87#define CAPS_LAST (CAPS_SIZE - 1)
88
89static slab_cache_t *cap_cache;
90
91static size_t caps_hash(const ht_link_t *item)
92{
93 cap_t *cap = hash_table_get_inst(item, cap_t, caps_link);
94 return hash_mix(CAP_HANDLE_RAW(cap->handle));
95}
96
97static size_t caps_key_hash(void *key)
98{
99 cap_handle_t *handle = (cap_handle_t *) key;
100 return hash_mix(CAP_HANDLE_RAW(*handle));
101}
102
103static bool caps_key_equal(void *key, const ht_link_t *item)
104{
105 cap_handle_t *handle = (cap_handle_t *) key;
106 cap_t *cap = hash_table_get_inst(item, cap_t, caps_link);
107 return *handle == cap->handle;
108}
109
110static hash_table_ops_t caps_ops = {
111 .hash = caps_hash,
112 .key_hash = caps_key_hash,
113 .key_equal = caps_key_equal
114};
115
116void caps_init(void)
117{
118 cap_cache = slab_cache_create("cap_t", sizeof(cap_t), 0, NULL,
119 NULL, 0);
120}
121
122/** Allocate the capability info structure
123 *
124 * @param task Task for which to allocate the info structure.
125 */
126errno_t caps_task_alloc(task_t *task)
127{
128 task->cap_info = (cap_info_t *) malloc(sizeof(cap_info_t),
129 FRAME_ATOMIC);
130 if (!task->cap_info)
131 return ENOMEM;
132 task->cap_info->handles = ra_arena_create();
133 if (!task->cap_info->handles)
134 goto error_handles;
135 if (!ra_span_add(task->cap_info->handles, CAPS_START, CAPS_SIZE))
136 goto error_span;
137 if (!hash_table_create(&task->cap_info->caps, 0, 0, &caps_ops))
138 goto error_span;
139 return EOK;
140
141error_span:
142 ra_arena_destroy(task->cap_info->handles);
143error_handles:
144 free(task->cap_info);
145 return ENOMEM;
146}
147
148/** Initialize the capability info structure
149 *
150 * @param task Task for which to initialize the info structure.
151 */
152void caps_task_init(task_t *task)
153{
154 mutex_initialize(&task->cap_info->lock, MUTEX_RECURSIVE);
155
156 for (kobject_type_t t = 0; t < KOBJECT_TYPE_MAX; t++)
157 list_initialize(&task->cap_info->type_list[t]);
158}
159
160/** Deallocate the capability info structure
161 *
162 * @param task Task from which to deallocate the info structure.
163 */
164void caps_task_free(task_t *task)
165{
166 hash_table_destroy(&task->cap_info->caps);
167 ra_arena_destroy(task->cap_info->handles);
168 free(task->cap_info);
169}
170
171/** Invoke callback function on task's capabilites of given type
172 *
173 * @param task Task where the invocation should take place.
174 * @param type Kernel object type of the task's capabilities that will be
175 * subject to the callback invocation.
176 * @param cb Callback function.
177 * @param arg Argument for the callback function.
178 *
179 * @return True if the callback was called on all matching capabilities.
180 * @return False if the callback was applied only partially.
181 */
182bool caps_apply_to_kobject_type(task_t *task, kobject_type_t type,
183 bool (*cb)(cap_t *, void *), void *arg)
184{
185 bool done = true;
186
187 mutex_lock(&task->cap_info->lock);
188 list_foreach_safe(task->cap_info->type_list[type], cur, next) {
189 cap_t *cap = list_get_instance(cur, cap_t, type_link);
190 done = cb(cap, arg);
191 if (!done)
192 break;
193 }
194 mutex_unlock(&task->cap_info->lock);
195
196 return done;
197}
198
199/** Initialize capability and associate it with its handle
200 *
201 * @param cap Address of the capability.
202 * @param task Backling to the owning task.
203 * @param handle Capability handle.
204 */
205static void cap_initialize(cap_t *cap, task_t *task, cap_handle_t handle)
206{
207 cap->state = CAP_STATE_FREE;
208 cap->task = task;
209 cap->handle = handle;
210 link_initialize(&cap->type_link);
211}
212
213/** Get capability using capability handle
214 *
215 * @param task Task whose capability to get.
216 * @param handle Capability handle of the desired capability.
217 * @param state State in which the capability must be.
218 *
219 * @return Address of the desired capability if it exists and its state matches.
220 * @return NULL if no such capability exists or it's in a different state.
221 */
222static cap_t *cap_get(task_t *task, cap_handle_t handle, cap_state_t state)
223{
224 assert(mutex_locked(&task->cap_info->lock));
225
226 if ((CAP_HANDLE_RAW(handle) < CAPS_START) ||
227 (CAP_HANDLE_RAW(handle) > CAPS_LAST))
228 return NULL;
229 ht_link_t *link = hash_table_find(&task->cap_info->caps, &handle);
230 if (!link)
231 return NULL;
232 cap_t *cap = hash_table_get_inst(link, cap_t, caps_link);
233 if (cap->state != state)
234 return NULL;
235 return cap;
236}
237
238/** Allocate new capability
239 *
240 * @param task Task for which to allocate the new capability.
241 *
242 * @param[out] handle New capability handle on success.
243 *
244 * @return An error code in case of error.
245 */
246errno_t cap_alloc(task_t *task, cap_handle_t *handle)
247{
248 mutex_lock(&task->cap_info->lock);
249 cap_t *cap = slab_alloc(cap_cache, FRAME_ATOMIC);
250 if (!cap) {
251 mutex_unlock(&task->cap_info->lock);
252 return ENOMEM;
253 }
254 uintptr_t hbase;
255 if (!ra_alloc(task->cap_info->handles, 1, 1, &hbase)) {
256 slab_free(cap_cache, cap);
257 mutex_unlock(&task->cap_info->lock);
258 return ENOMEM;
259 }
260 cap_initialize(cap, task, (cap_handle_t) hbase);
261 hash_table_insert(&task->cap_info->caps, &cap->caps_link);
262
263 cap->state = CAP_STATE_ALLOCATED;
264 *handle = cap->handle;
265 mutex_unlock(&task->cap_info->lock);
266
267 return EOK;
268}
269
270/** Publish allocated capability
271 *
272 * The kernel object is moved into the capability. In other words, its reference
273 * is handed over to the capability. Once published, userspace can access and
274 * manipulate the capability.
275 *
276 * @param task Task in which to publish the capability.
277 * @param handle Capability handle.
278 * @param kobj Kernel object.
279 */
280void
281cap_publish(task_t *task, cap_handle_t handle, kobject_t *kobj)
282{
283 mutex_lock(&task->cap_info->lock);
284 cap_t *cap = cap_get(task, handle, CAP_STATE_ALLOCATED);
285 assert(cap);
286 cap->state = CAP_STATE_PUBLISHED;
287 /* Hand over kobj's reference to cap */
288 cap->kobject = kobj;
289 list_append(&cap->type_link, &task->cap_info->type_list[kobj->type]);
290 mutex_unlock(&task->cap_info->lock);
291}
292
293/** Unpublish published capability
294 *
295 * The kernel object is moved out of the capability. In other words, the
296 * capability's reference to the objects is handed over to the kernel object
297 * pointer returned by this function. Once unpublished, the capability does not
298 * refer to any kernel object anymore.
299 *
300 * @param task Task in which to unpublish the capability.
301 * @param handle Capability handle.
302 * @param type Kernel object type of the object associated with the
303 * capability.
304 */
305kobject_t *cap_unpublish(task_t *task, cap_handle_t handle, kobject_type_t type)
306{
307 kobject_t *kobj = NULL;
308
309 mutex_lock(&task->cap_info->lock);
310 cap_t *cap = cap_get(task, handle, CAP_STATE_PUBLISHED);
311 if (cap) {
312 if (cap->kobject->type == type) {
313 /* Hand over cap's reference to kobj */
314 kobj = cap->kobject;
315 cap->kobject = NULL;
316 list_remove(&cap->type_link);
317 cap->state = CAP_STATE_ALLOCATED;
318 }
319 }
320 mutex_unlock(&task->cap_info->lock);
321
322 return kobj;
323}
324
325/** Free allocated capability
326 *
327 * @param task Task in which to free the capability.
328 * @param handle Capability handle.
329 */
330void cap_free(task_t *task, cap_handle_t handle)
331{
332 assert(CAP_HANDLE_RAW(handle) >= CAPS_START);
333 assert(CAP_HANDLE_RAW(handle) <= CAPS_LAST);
334
335 mutex_lock(&task->cap_info->lock);
336 cap_t *cap = cap_get(task, handle, CAP_STATE_ALLOCATED);
337
338 assert(cap);
339
340 hash_table_remove_item(&task->cap_info->caps, &cap->caps_link);
341 ra_free(task->cap_info->handles, CAP_HANDLE_RAW(handle), 1);
342 slab_free(cap_cache, cap);
343 mutex_unlock(&task->cap_info->lock);
344}
345
346/** Initialize kernel object
347 *
348 * @param kobj Kernel object to initialize.
349 * @param type Type of the kernel object.
350 * @param raw Raw pointer to the encapsulated object.
351 * @param ops Pointer to kernel object operations for the respective type.
352 */
353void kobject_initialize(kobject_t *kobj, kobject_type_t type, void *raw,
354 kobject_ops_t *ops)
355{
356 atomic_set(&kobj->refcnt, 1);
357 kobj->type = type;
358 kobj->raw = raw;
359 kobj->ops = ops;
360}
361
362/** Get new reference to kernel object from capability
363 *
364 * @param task Task from which to get the reference.
365 * @param handle Capability handle.
366 * @param type Kernel object type of the object associated with the
367 * capability referenced by handle.
368 *
369 * @return Kernel object with incremented reference count on success.
370 * @return NULL if there is no matching capability or kernel object.
371 */
372kobject_t *
373kobject_get(struct task *task, cap_handle_t handle, kobject_type_t type)
374{
375 kobject_t *kobj = NULL;
376
377 mutex_lock(&task->cap_info->lock);
378 cap_t *cap = cap_get(task, handle, CAP_STATE_PUBLISHED);
379 if (cap) {
380 if (cap->kobject->type == type) {
381 kobj = cap->kobject;
382 atomic_inc(&kobj->refcnt);
383 }
384 }
385 mutex_unlock(&task->cap_info->lock);
386
387 return kobj;
388}
389
390/** Record new reference
391 *
392 * @param kobj Kernel object from which the new reference is created.
393 */
394void kobject_add_ref(kobject_t *kobj)
395{
396 atomic_inc(&kobj->refcnt);
397}
398
399/** Drop reference to kernel object
400 *
401 * The encapsulated object and the kobject_t wrapper are both destroyed when the
402 * last reference is dropped.
403 *
404 * @param kobj Kernel object whose reference to drop.
405 */
406void kobject_put(kobject_t *kobj)
407{
408 if (atomic_postdec(&kobj->refcnt) == 1) {
409 kobj->ops->destroy(kobj->raw);
410 free(kobj);
411 }
412}
413
414/** @}
415 */
Note: See TracBrowser for help on using the repository browser.