How does Nouveau driver allocate memory on the GPU side?

Viewed 148

I've been looking into the Nouveau GPU driver (open-source GPU driver for Nvidia GPUs) because I'd like the understand what's actually going on inside the driver. Specifically, I've been trying to figure out how the Nouveau code running on the CPU side would be able to allocate memory on the GPU side.
Put differently, I want to learn how cudaMalloc() is able to allocate memory on the GPU side. (I know cudaMalloc() is CUDA API, but I'm not sure what the equivalent of CUDA's cudaMalloc() is on Nouveau)
Here's what I've found out so far:

  1. Nouveau has the TTM (translation table manager) initialize the bo (buffer object) by int ttm_bo_init(...) whose definition can be found here and is given by:
int ttm_bo_init(struct ttm_bo_device *bdev,
        struct ttm_buffer_object *bo,
        unsigned long size,
        enum ttm_bo_type type,
        struct ttm_placement *placement,
        uint32_t page_alignment,
        unsigned long buffer_start,
        bool interruptible,
        struct file *persistent_swap_storage,
        size_t acc_size,
        void (*destroy) (struct ttm_buffer_object *))
{
    int ret = 0;
    unsigned long num_pages;
    struct ttm_mem_global *mem_glob = bdev->glob->mem_glob;

    ret = ttm_mem_global_alloc(mem_glob, acc_size, false, false);
    if (ret) {
        printk(KERN_ERR TTM_PFX "Out of kernel memory.\n");
        if (destroy)
            (*destroy)(bo);
        else
            kfree(bo);
        return -ENOMEM;
    }

    size += buffer_start & ~PAGE_MASK;
    num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
    if (num_pages == 0) {
        printk(KERN_ERR TTM_PFX "Illegal buffer object size.\n");
        if (destroy)
            (*destroy)(bo);
        else
            kfree(bo);
        return -EINVAL;
    }
    bo->destroy = destroy;

    kref_init(&bo->kref);
    kref_init(&bo->list_kref);
    atomic_set(&bo->cpu_writers, 0);
    atomic_set(&bo->reserved, 1);
    init_waitqueue_head(&bo->event_queue);
    INIT_LIST_HEAD(&bo->lru);
    INIT_LIST_HEAD(&bo->ddestroy);
    INIT_LIST_HEAD(&bo->swap);
    INIT_LIST_HEAD(&bo->io_reserve_lru);
    bo->bdev = bdev;
    bo->glob = bdev->glob;
    bo->type = type;
    bo->num_pages = num_pages;
    bo->mem.size = num_pages << PAGE_SHIFT;
    bo->mem.mem_type = TTM_PL_SYSTEM;
    bo->mem.num_pages = bo->num_pages;
    bo->mem.mm_node = NULL;
    bo->mem.page_alignment = page_alignment;
    bo->mem.bus.io_reserved_vm = false;
    bo->mem.bus.io_reserved_count = 0;
    bo->buffer_start = buffer_start & PAGE_MASK;
    bo->priv_flags = 0;
    bo->mem.placement = (TTM_PL_FLAG_SYSTEM | TTM_PL_FLAG_CACHED);
    bo->seq_valid = false;
    bo->persistent_swap_storage = persistent_swap_storage;
    bo->acc_size = acc_size;
    atomic_inc(&bo->glob->bo_count);

    ret = ttm_bo_check_placement(bo, placement);
    if (unlikely(ret != 0))
        goto out_err;

    /*
     * For ttm_bo_type_device buffers, allocate
     * address space from the device.
     */
    if (bo->type == ttm_bo_type_device) {
        ret = ttm_bo_setup_vm(bo);
        if (ret)
            goto out_err;
    }

    ret = ttm_bo_validate(bo, placement, interruptible, false, false);
    if (ret)
        goto out_err;

    ttm_bo_unreserve(bo);
    return 0;

out_err:
    ttm_bo_unreserve(bo);
    ttm_bo_unref(&bo);

    return ret;
}

I think that for user-space applications, the buffer would be allocated on the device (GPU) side at this part of the code via ttm_bo_setup_vm():

    /*
     * For ttm_bo_type_device buffers, allocate
     * address space from the device.
     */
    if (bo->type == ttm_bo_type_device) {
        ret = ttm_bo_setup_vm(bo);
        if (ret)
            goto out_err;
    }

And ttm_bo_setup_vm() is defined as:

/**
 * ttm_bo_setup_vm:
 *
 * @bo: the buffer to allocate address space for
 *
 * Allocate address space in the drm device so that applications
 * can mmap the buffer and access the contents. This only
 * applies to ttm_bo_type_device objects as others are not
 * placed in the drm device address space.
 */

static int ttm_bo_setup_vm(struct ttm_buffer_object *bo)
{
    struct ttm_bo_device *bdev = bo->bdev;
    int ret;

retry_pre_get:
    ret = drm_mm_pre_get(&bdev->addr_space_mm);
    if (unlikely(ret != 0))
        return ret;

    write_lock(&bdev->vm_lock);
    bo->vm_node = drm_mm_search_free(&bdev->addr_space_mm,
                     bo->mem.num_pages, 0, 0);

    if (unlikely(bo->vm_node == NULL)) {
        ret = -ENOMEM;
        goto out_unlock;
    }

    bo->vm_node = drm_mm_get_block_atomic(bo->vm_node,
                          bo->mem.num_pages, 0);

    if (unlikely(bo->vm_node == NULL)) {
        write_unlock(&bdev->vm_lock);
        goto retry_pre_get;
    }

    ttm_bo_vm_insert_rb(bo);
    write_unlock(&bdev->vm_lock);
    bo->addr_space_offset = ((uint64_t) bo->vm_node->start) << PAGE_SHIFT;

    return 0;
out_unlock:
    write_unlock(&bdev->vm_lock);
    return ret;
}

But I'm having trouble finding out how the code inside lock-and-unlock part is allocating memory on the GPU side.
Also, I've stumbled upon this question which was asked nearly 10 years ago, and would like to know if the answer is still valid - if regular malloc() and free() can be used to allocate and free memory on the GPU side.

I know the question is somewhat vague but I don't know what other information I can provide to make it more clear. Any pointers or thoughts will be greatly appreciated.
Thanks in advance!

0 Answers
Related