1/*
2 * Copyright (c) 2014 ETH Zurich.
3 * All rights reserved.
4 *
5 * This file is distributed under the terms in the attached LICENSE file.
6 * If you do not find this file, copies can be found by writing to:
7 * ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
8 */
9
10#include <string.h>
11#include <barrelfish/barrelfish.h>
12
13#include <dma_internal.h>
14#include <dma/dma_mem_mgr.h>
15
16#include <debug.h>
17
18/*
19 * XXX: we are using a ordered linked list here instead of a proper tree structure
20 *      assumption: the number of registered memory regions per binding is low
21 *      and thus the lookup times acceptable low.
22 */
23
24/**
25 * represents a tree of registered memory regions
26 */
27struct dma_mem_tree
28{
29    struct dma_mem_node *root;
30    size_t num_entries;
31};
32
33/**
34 * represents a registered memory region
35 */
36struct dma_mem_node
37{
38    struct dma_mem_node *prev;  ///< the previous region
39    struct dma_mem_node *next;  ///< the next region
40    struct capref cap;          ///< capability backing this region
41    lpaddr_t paddr;             ///< converted physical address of the region
42    size_t size;                ///< cached size in bytes of the region
43};
44
45/**
46 * represents the state of a DMA memory region manager
47 */
48struct dma_mem_mgr
49{
50    struct dma_mem_tree regions;  ///< registered memory regions
51    lpaddr_t range_min;          ///< minimum allowed memory address
52    lpaddr_t range_max;          ///< maximum allowed memory address
53    dma_mem_convert_fn convert;  ///< converts the registered address to a dma usable
54    void *convert_arg;           ///< argument for the convert function
55};
56
57/*
58 * ----------------------------------------------------------------------------
59 * Memory Region Management
60 * ----------------------------------------------------------------------------
61 */
62static errval_t mem_tree_init(struct dma_mem_tree *tree)
63{
64    tree->root = NULL;
65    tree->num_entries = 0;
66
67    return SYS_ERR_OK;
68}
69
70/*
71 * returns the closest element to a given address.
72 *
73 * \returns dma_mem_node the previous element
74 *          NULL if the closest would be before the first element
75 */
76static struct dma_mem_node *mem_tree_find_nearest(struct dma_mem_tree *tree,
77                                                  lpaddr_t base)
78{
79    struct dma_mem_node *node = tree->root;
80    struct dma_mem_node *ret = tree->root;
81    assert(node);
82    while (node) {
83        if (node->paddr > base) {
84            return node->prev;
85        }
86        ret = node;
87        node = node->next;
88    }
89    assert(ret);
90    return ret;
91}
92
93static struct dma_mem_node *mem_tree_lookup(struct dma_mem_tree *tree,
94                                            lpaddr_t base,
95                                            size_t size)
96{
97    if (tree->root == NULL) {
98        return NULL;
99    }
100
101    struct dma_mem_node *node = mem_tree_find_nearest(tree, base);
102    if (node == NULL) {
103        return NULL;
104    }
105    if (node->paddr <= base) {
106        /* the base may be within the region of this node. check size */
107        if ((node->paddr + node->size) >= (base + size)) {
108            /* its within the registered region. Found!*/
109            return node;
110        }
111    }
112    return NULL;
113}
114
115static errval_t mem_tree_insert(struct dma_mem_tree *tree,
116                                struct dma_mem_node *entry)
117{
118    if (tree->root == NULL) {
119        assert(tree->num_entries == 0);
120        entry->next = NULL;
121        entry->prev = NULL;
122        tree->root = entry;
123        tree->num_entries = 1;
124        return SYS_ERR_OK;
125    }
126
127    struct dma_mem_node *node = mem_tree_find_nearest(tree, entry->paddr);
128
129    if (node == NULL) {
130        /* insert at the beginning */
131        entry->prev = NULL;
132        entry->next = tree->root;
133        tree->root->prev = entry;
134        tree->num_entries++;
135        return SYS_ERR_OK;
136    }
137    if (node->paddr <= entry->paddr) {
138        /* the base may be within the region of this node. check size */
139        if ((node->paddr + node->size) >= (entry->paddr + entry->size)) {
140            /* this would lie within a already registered region */
141            return DMA_ERR_MEM_OVERLAP;
142        }
143    }
144
145    if (node->next) {
146        node->next->prev = entry;
147    }
148    entry->prev = node;
149    entry->next = node->next;
150    node->next = entry;
151    tree->num_entries++;
152
153    return SYS_ERR_OK;
154}
155
156static struct dma_mem_node *mem_tree_remove(struct dma_mem_tree *tree,
157                                            lpaddr_t base)
158{
159    if (tree->root == NULL) {
160        assert(tree->num_entries == 0);
161        return NULL;
162    }
163    struct dma_mem_node *node = mem_tree_lookup(tree, base, 0);
164    if (node == NULL) {
165        return NULL;
166    }
167
168    if (node->prev) {
169        node->prev->next = node->next;
170    } else {
171        tree->root = node->next;
172    }
173
174    if (node->next) {
175        node->next->prev = node->prev;
176    }
177
178    assert(tree->root || (tree->num_entries == 1));
179
180    tree->num_entries--;
181
182    node->next = NULL;
183    node->prev = NULL;
184
185    return node;
186}
187
188/*
189 * ============================================================================
190 * Public Interface
191 * ============================================================================
192 */
193
194/**
195 * \brief initializes the DMA memory region manager
196 *
197 * \param mem_mgr   returned pointer to the mem manager structure
198 * \param range_min minimum allowed memory address
199 * \param range_max maximum allowed memory address
200 *
201 * \returns SYS_ERR_OK on success
202 *          errval on failure
203 */
204errval_t dma_mem_mgr_init(struct dma_mem_mgr **mem_mgr,
205                          lpaddr_t range_min,
206                          lpaddr_t range_max)
207{
208    errval_t err;
209
210    struct dma_mem_mgr *mgr = calloc(1, sizeof(*mgr));
211    if (mgr == NULL) {
212        return LIB_ERR_MALLOC_FAIL;
213    }
214
215    err = mem_tree_init(&mgr->regions);
216    if (err_is_fail(err)) {
217        free(mgr);
218        return err;
219    }
220
221    mgr->range_max = range_max;
222    mgr->range_min = range_min;
223
224    *mem_mgr = mgr;
225
226    return SYS_ERR_OK;
227}
228
229/**
230 * \brief sets the address conversion function to be used for translating the
231 *        addresses
232 *
233 * \param mem_mgr   DMA memory manager
234 * \param fn        convert function to be called
235 * \param arg       argument supplied for the convert function
236 */
237void dma_mem_mgr_set_convert_fn(struct dma_mem_mgr *mem_mgr,
238                                dma_mem_convert_fn fn,
239                                void *arg)
240{
241    mem_mgr->convert = fn;
242    mem_mgr->convert_arg = arg;
243}
244
245/**
246 * \brief registers a memory region to be used for DMA transfers
247 *
248 * \param mem_mgr   DMA memory manager
249 * \param cap       frame capability of the memory region to register
250 *
251 * \returns SYS_ERR_OK on success
252 *          errval on failure
253 */
254errval_t dma_mem_register(struct dma_mem_mgr *mem_mgr,
255                          struct capref cap, genpaddr_t *retaddr)
256{
257    errval_t err;
258
259    struct frame_identity frame_id;
260    err = frame_identify(cap, &frame_id);
261    if (err_is_fail(err)) {
262        return err;
263    }
264
265    DMAMEM_DEBUG("registering DMA memory range [0x%016lx, 0x%016lx]\n",
266                 frame_id.base, frame_id.base + frame_id.bytes);
267
268    struct dma_mem_node *entry = calloc(1, sizeof(*entry));
269    if (entry == NULL) {
270        return LIB_ERR_MALLOC_FAIL;
271    }
272
273    entry->cap = cap;
274    entry->paddr = frame_id.base;
275    entry->size = frame_id.bytes;
276
277    if (mem_mgr->convert) {
278        err = mem_mgr->convert(mem_mgr->convert_arg, cap, &entry->paddr, NULL);
279        DMAMEM_DEBUG("converted base address [0x%016lx] -> [0x%016lx]\n",
280                     frame_id.base, entry->paddr);
281        if (err_is_fail(err)) {
282            return err;
283        }
284    }
285
286    if ((entry->paddr == 0) || (entry->paddr < mem_mgr->range_min)
287        || ((entry->paddr + entry->size) > mem_mgr->range_max)) {
288        free(entry);
289        return DMA_ERR_MEM_OUT_OF_RANGE;
290    }
291
292    err = mem_tree_insert(&mem_mgr->regions, entry);
293    if (err_is_fail(err)) {
294        free(entry);
295        return err;
296    }
297
298    if (retaddr) {
299        *retaddr = entry->paddr;
300    }
301
302    return SYS_ERR_OK;
303
304}
305
306/**
307 * \brief deregisters a memory region that it cannot longer be used for the
308 *        memory manager
309 *
310 * \param mem_mgr   DMA memory manager
311 * \param cap       frame capability of the memory region to register
312 *
313 * \returns SYS_ERR_OK on success
314 *          errval on failure
315 */
316errval_t dma_mem_deregister(struct dma_mem_mgr *mem_mgr,
317                            struct capref cap)
318{
319    errval_t err;
320
321    struct frame_identity frame_id;
322    err = frame_identify(cap, &frame_id);
323    if (err_is_fail(err)) {
324        return err;
325    }
326
327    DMAMEM_DEBUG("deregister DMA memory range [0x%016lx, 0x%016lx]\n",
328                 frame_id.base, frame_id.base + frame_id.bytes);
329
330    lpaddr_t addr = frame_id.base;
331    if (mem_mgr->convert) {
332        err = mem_mgr->convert(mem_mgr->convert_arg, cap, &addr, NULL);
333        DMAMEM_DEBUG("converted base address [0x%016lx] -> [0x%016lx]\n",
334                     frame_id.base, addr);
335        if (err_is_fail(err)) {
336            return err;
337        }
338    }
339
340    if (addr == 0) {
341        return DMA_ERR_MEM_OUT_OF_RANGE;
342    }
343
344    struct dma_mem_node *entry = mem_tree_remove(&mem_mgr->regions, addr);
345    if (entry) {
346        free(entry);
347        return SYS_ERR_OK;
348    }
349
350    return DMA_ERR_MEM_NOT_REGISTERED;
351}
352
353/**
354 * \brief verifies if a addres-length pair lies completely within a
355 *        registered memory region and translates the address
356 *
357 * \param mem_mgr   DMA memory manager
358 * \param addr      address to be looked up
359 * \param bytes     length of the transfer in bytes
360 * \param dma_addr  translated base address (if change in address space)
361 *
362 * \returns SYS_ERR_OK on success
363 *          DMA_ERR_MEM_NOT_REGISTERED if the memory region is not registered
364 *          DMA_ERR_OUT_OF_RANGE if the memory region is out of range
365 */
366errval_t dma_mem_verify(struct dma_mem_mgr *mem_mgr,
367                        lpaddr_t addr,
368                        size_t bytes,
369                        lpaddr_t *dma_addr)
370{
371    lpaddr_t daddr = addr;
372# if 0
373    if (mem_mgr->convert) {
374        daddr = mem_mgr->convert(mem_mgr->convert_arg, addr, bytes);
375        DMAMEM_DEBUG("converted base address [0x%016lx] -> [0x%016lx]\n", addr,
376                     daddr);
377    }
378#endif
379
380    DMAMEM_DEBUG("Verify DMA memory range [0x%016lx, 0x%016lx]\n", daddr,
381                  daddr + bytes);
382
383    if ((daddr == 0) || (daddr < mem_mgr->range_min)
384        || ((daddr + bytes) > mem_mgr->range_max)) {
385        return DMA_ERR_MEM_OUT_OF_RANGE;
386    }
387
388    struct dma_mem_node *entry = mem_tree_lookup(&mem_mgr->regions, daddr, bytes);
389    if (entry) {
390        *dma_addr = daddr;
391        return SYS_ERR_OK;
392    }
393
394    return DMA_ERR_MEM_NOT_REGISTERED;
395}
396