Mecanismo completo de mapeamento de buffers para endereços DMA no framework Qualcomm AIS

Este artigo detalha o fluxo de mapeamento de buffers de memória do espaço do usuário para endereços de hardware (DMA) dentro do framework Qualcomm AIS (Automotive Imaging System), incluindo interações com o kernel através do V4L2.

Configuração inicial dos buffers no cliente AIS

O cliente AIS inicia o processo configurando os buffers de saída através da API pública. A função abaixo atua como um wrapper que delega para a implementação interna do motor AIS.

qcarcam_ret_t setup_camera_buffers(qcarcam_hndl_t handle, qcarcam_buffers_t* buffer_config)
{
    return translate_camera_result(initialize_camera_buffers(handle, buffer_config));
}

// Chamada exemplo no cliente:
setup_camera_buffers(client_context->device_handle, &client_context->output_buffer_config);

Validação e mapeamento de buffers no motor AIS

A função interna do motor AIS valida os parâmetros fornecidos e orquestra o processo de mapeamento. Ela verifica o estado do contexto do usuário, a quantidade de buffers e as flags de segurança antes de prosseguir com o mapeamento real.

CameraResult initialize_camera_buffers(qcarcam_hndl_t handle, qcarcam_buffers_t* buffer_config)
{
    CameraResult status = CAMERA_SUCCESS;
    UserContext* usr_ctxt = obtain_user_context(handle);

    if (!usr_ctxt) {
        return CAMERA_EBADHANDLE;
    }

    usr_ctxt->lock();

    if (!buffer_config) {
        status = CAMERA_EMEMPTR;
    } else if (usr_ctxt->state != CONTEXT_OPENED && usr_ctxt->state != CONTEXT_RESERVED) {
        status = CAMERA_EBADSTATE;
    } else if (buffer_config->num_buffers < MIN_BUFFER_COUNT || buffer_config->num_buffers > usr_ctxt->max_buffers) {
        status = CAMERA_EBADPARM;
    } else if (check_security_flags_mismatch(usr_ctxt->is_secure, buffer_config->flags)) {
        status = CAMERA_EBADPARM;
    } else {
        BufferDescriptorList* descriptor_list = usr_ctxt->output_descriptor_list;
        if (descriptor_list->count > 0) {
            unmap_existing_buffers(descriptor_list);
        }
        status = perform_buffer_mapping(descriptor_list, buffer_config);
    }

    usr_ctxt->unlock();
    release_user_context(usr_ctxt);
    return status;
}

Processo de mapeamento e extração de metadados do buffer

Esta função itera sobre cada buffer fornecido, extrai informações sobre os planos de memória e utiliza uma API de baixo nível para realizar o mapeamento no SMMU (System Memory Management Unit). Em caso de falha, ela desfaz o mapeamento dos buffers já processados.

CameraResult perform_buffer_mapping(BufferDescriptorList* list, qcarcam_buffers_t* user_buffers)
{
    CameraResult mapping_status = CAMERA_SUCCESS;
    CameraHardwareBlock target_block = CAMERA_HW_IFE;

    for (int idx = 0; idx < (int)user_buffers->num_buffers; ++idx) {
        BufferDescriptor* descriptor = &list->descriptors[idx];

        descriptor->index = idx;
        descriptor->user_pointer = user_buffers->buffers[idx].planes[0].user_data;
        descriptor->format = user_buffers->color_format;
        descriptor->flags = translate_buffer_flags(user_buffers->flags);

        if (user_buffers->buffers[idx].plane_count <= QCARCAM_MAX_PLANES) {
            size_t total_size = 0;
            descriptor->plane_info.count = user_buffers->buffers[idx].plane_count;

            for (uint32 plane_idx = 0; plane_idx < user_buffers->buffers[idx].plane_count; ++plane_idx) {
                auto& plane = descriptor->plane_info.planes[plane_idx];
                auto& src_plane = user_buffers->buffers[idx].planes[plane_idx];

                plane.width = src_plane.width;
                plane.height = src_plane.height;
                plane.stride = src_plane.stride;
                plane.size = src_plane.size;
                plane.user_handle = (unsigned long long)src_plane.user_data;
                plane.offset = total_size;
                total_size += src_plane.size;
            }
            descriptor->total_size = total_size;
        }

        mapping_status = map_buffer_to_hardware(descriptor, descriptor->flags, &target_block, 1);
        if (mapping_status != CAMERA_SUCCESS) {
            for (int cleanup_idx = idx - 1; cleanup_idx >= 0; --cleanup_idx) {
                hardware_buffer_unmap(&list->descriptors[cleanup_idx]);
            }
            memset(list->descriptors, 0, sizeof(list->descriptors));
            break;
        }

        descriptor->hardware_address = retrieve_device_address(descriptor, target_block);
        descriptor->is_externally_allocated = FALSE;
        descriptor->mapping_state = BUFFER_MAPPED;
    }

    if (mapping_status == CAMERA_SUCCESS) {
        list->update_properties(user_buffers->num_buffers,
            user_buffers->buffers[0].planes[0].width,
            user_buffers->buffers[0].planes[0].height,
            user_buffers->color_format);
    }
    return mapping_status;
}

Chamada ioctl para mapeamento no contexto SMMU

A requisição de mapeamento é empacotada e enviada ao kernel através de um comando ioctl V4L2 genérico. A estrutura de comando é populada, e o descritor de arquivo do buffer é convertido em um inteiro para passagem ao kernel.

CameraResult map_buffer_to_hardware(BufferDescriptor* descriptor, uint32 flags,
    const CameraHardwareBlock* hw_blocks, uint32 block_count)
{
    if (!descriptor || !descriptor->user_pointer || !hw_blocks) {
        return CAMERA_EBADPARM;
    }

    struct camera_memory_map_request request = {};
    populate_mmu_handles(request.mmu_handles, &request.handle_count, flags, hw_blocks, block_count);

    request.flags = HW_READ_WRITE;
    if (flags & SECURE_BUFFER_FLAG) {
        request.flags |= CP_PIXEL_PROTECTION;
    }
    request.buffer_fd = (int)(uintptr_t)descriptor->user_pointer;

    lock_platform_allocator();
    CameraResult result = platform_context->request_manager->execute_ioctl(
        CAMERA_REQUEST_MANAGER_MAP_BUFFER, &request, 0, sizeof(request));
    unlock_platform_allocator();

    if (result == CAMERA_SUCCESS) {
        descriptor->hardware_address = request.output.handle;
        if (!(flags & SECURE_BUFFER_FLAG)) {
            descriptor->virtual_address = map_memory_region(request.buffer_fd, descriptor->total_size, 0);
        }
    }
    return result;
}

// Implementação do método de ioctl no gerenciador de requisições:
CameraResult RequestManager::execute_ioctl(uint32 operation, void* payload, uint32 reserved, uint32 size)
{
    struct camera_ioctl_control command;
    command.opcode = operation;
    command.payload_size = size;
    command.handle_type = USER_POINTER_HANDLE;
    command.payload_ptr = pointer_to_uint64(payload);

    int ioctl_result = ioctl(this->request_manager_fd, VIDIOC_CAMERA_CONTROL, &command);
    return (ioctl_result == 0) ? CAMERA_SUCCESS : CAMERA_EFAILED;
}

Roteamento do comando no kernel via V4L2

O comando ioctl V4L2 genérico é roteado dentro do kernel. Para comandos desconhecidos, o framework V4L2 chama a operação padrão 'vidioc_default', que neste caso é implemnetada pelo controlador de câmera específico para processar comandos de memória.

// Função principal de despacho de ioctl do V4L2 (simplificado):
static long v4l2_ioctl_dispatch(struct file *file, unsigned int cmd, void *arg)
{
    // ... lógica de verificação de comando e mutex ...
    if (is_known_ioctl(cmd)) {
        // executa operação conhecida
    } else if (ioctl_ops->vidioc_default) {
        return ioctl_ops->vidioc_default(file, file->private_data, 0, cmd, arg);
    }
    return -ENOTTY;
}

// Handler padrão do controlador de câmera para comandos privados:
static long camera_private_command_handler(struct file *file, void *fh, bool valid_priority,
    unsigned int cmd, void *arg)
{
    struct camera_ioctl_control *ioctl_cmd = (struct camera_ioctl_control *)arg;

    if (cmd != VIDIOC_CAMERA_CONTROL || !ioctl_cmd || !ioctl_cmd->payload_ptr) {
        return -EINVAL;
    }

    switch (ioctl_cmd->opcode) {
    case CAMERA_REQUEST_MANAGER_MAP_BUFFER: {
        struct camera_memory_map_request request;
        // Copia a estrutura de mapeamento do espaço do usuário
        if (copy_from_user(&request, uint64_to_user_ptr(ioctl_cmd->payload_ptr), sizeof(request))) {
            return -EFAULT;
        }
        int rc = process_dma_buffer_mapping(&request);
        if (!rc) {
            // Copia a estrutura atualizada de volta para o espaço do usuário
            if (copy_to_user(uint64_to_user_ptr(ioctl_cmd->payload_ptr), &request, sizeof(request))) {
                return -EFAULT;
            }
        }
        return rc;
    }
    // ... outros casos ...
    }
}

Mapeamento do buffer DMA no kernel

A função principle do kernel primeiro obtém uma referência ao dma_buf a partir do file descriptor, realiza verificações de segurança e flags, e então chama as rotinas de SMMU para mapear o buffer na memória do hardware.

int process_dma_buffer_mapping(struct camera_memory_map_request *cmd)
{
    int status = 0;
    struct dma_buf *dma_buffer = NULL;
    dma_addr_t hardware_virtual_address = 0;
    size_t mapped_length = 0;

    // Validação inicial dos parâmetros
    if (!cmd || cmd->buffer_fd < 0 || cmd->handle_count > MAX_MMU_HANDLES) {
        return -EINVAL;
    }
    if (validate_mapping_flags(cmd->flags) != 0) {
        return -EINVAL;
    }

    dma_buffer = dma_buf_get(cmd->buffer_fd);
    if (IS_ERR_OR_NULL(dma_buffer)) {
        return -EINVAL;
    }

    // Mapeamento para acesso do hardware
    if (cmd->flags & (HW_READ_WRITE | PROTECTED_MODE)) {
        status = map_buffer_for_device_access(cmd->flags, cmd->mmu_handles, cmd->handle_count,
            cmd->buffer_fd, dma_buffer, &hardware_virtual_address, &mapped_length, CAMERA_SMMU_IO_REGION);
        if (status) {
            dma_buf_put(dma_buffer);
            return status;
        }
    }

    // Aloca um slot na tabela de buffers do kernel
    int table_index = allocate_buffer_slot();
    if (table_index < 0) {
        dma_buf_put(dma_buffer);
        return -ENOMEM;
    }

    // Popula a entrada na tabela
    lock_buffer_table_slot(table_index);
    buffer_table[table_index].fd = cmd->buffer_fd;
    buffer_table[table_index].dma_buf_ptr = dma_buffer;
    buffer_table[table_index].flags = cmd->flags;
    buffer_table[table_index].handle = create_memory_handle(table_index, cmd->buffer_fd);
    buffer_table[table_index].hardware_address = hardware_virtual_address;
    buffer_table[table_index].mapped_length = mapped_length;
    buffer_table[table_index].mmu_handle_count = cmd->handle_count;
    memcpy(buffer_table[table_index].mmu_handles, cmd->mmu_handles,
        sizeof(int32_t) * cmd->handle_count);
    unlock_buffer_table_slot(table_index);

    // Preenche a resposta
    cmd->output.handle = buffer_table[table_index].handle;
    return 0;
}

// A função map_buffer_for_device_access segue uma cadeia de chamadas para configurar o SMMU:
// map_buffer_for_device_access -> configure_device_iova -> map_and_link_buffer -> validate_smmu_mapping
// O objetivo final é configurar o SMMU para permitir que o hardware de câmera acesse a memória física do buffer através de um endereço IOVA.

Tags: Qualcomm Camera AIS V4L2 DMA-BUF Camera Buffer Mapping

Publicado em 6-25 19:59