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.