У нас есть потоковая видеокамера. API реализован как библиотека пользовательского режима, он передает ioctl s для получения информации о камере, захвата видеокадров и т. Д.
Также у нас есть два потока - каждый имеет отдельный указатель файла. Один поток передает команды, другой захватывает видеокадры. С точки зрения драйвера некоторые запросы могут выполняться параллельно, тогда как другие должны быть сериализованы.
В драйвере мы хотим отобразить входные и выходные пользовательские буферы в пространство ядра, чтобы избежать использования функций copy_to_user
, copy_from_user
.
Выходной буфер, содержащий видеокадр, может быть довольно большим: более 5 МБ, он будет занимать почти 2462 страницы.
Идея состоит в том, чтобы выделить хранилище для пользовательских страниц для каждого имеющегося у нас указателя файла и отобразить пользовательские буферы внутри ioctl. Вот код:
#define PAGES_REQUIRED(size) (PAGE_ALIGN(size) / PAGE_SIZE)
#define PAGES_ADDR(ptr) ((PAGE_ALIGN(ptr) > ptr) ? (PAGE_ALIGN(ptr) - PAGE_SIZE) : ptr)
#define BUFFER_IN_MAX 10000
#define BUFFER_OUT_MAX (FRAMEINFO_SIZEF + MAX_FRAME_SIZE_16)
typedef struct _FILE_CONTEXT
{
PDEVICE_CONTEXT context;
struct page *in_pages[(PAGES_REQUIRED(BUFFER_IN_MAX) + 1)];
struct page *out_pages[(PAGES_REQUIRED(BUFFER_OUT_MAX) + 1) + (PAGES_REQUIRED(BUFFER_IN_MAX) + 1)];
} FILE_CONTEXT, *PFILE_CONTEXT;
struct user_packet
{
int in_size;
unsigned char *in_buffer;
int out_size;
unsigned char *out_buffer;
};
bool device_user_buffers_share_pages(uint8_t *user_addr_in, size_t user_size_in,
uint8_t *user_addr_out, size_t user_size_out)
{
uint8_t *user_page_addr_in = PAGES_ADDR((size_t)user_addr_in);
size_t user_aligned_size_in = user_size_in + (user_addr_in - user_page_addr_in);
uint8_t *user_page_addr_out = PAGES_ADDR((size_t)user_addr_out);
size_t user_aligned_size_out = user_size_out + (user_addr_out - user_page_addr_out);
return ((user_page_addr_in < (user_page_addr_out + user_aligned_size_out)) &&
(user_page_addr_out < (user_page_addr_in + user_aligned_size_in)));
}
status_t device_map_user_buffer(/*in*/uint8_t *user_addr, /*in*/size_t user_size, /*in*/bool read_only,
/*out*/struct page **out_user_pages, /*out*/size_t *out_user_pages_maped,
/*out*/uint8_t **out_kernel_base, /*out*/uint8_t **out_kernel_addr)
{
LogDebug("user_addr: %llu", user_addr);
LogDebug("user_size: %llu", user_size);
uint8_t *user_page_addr = PAGES_ADDR((size_t)user_addr);
LogDebug("user_page_addr: %llu", user_page_addr);
size_t user_page_offset = user_addr - user_page_addr;
LogDebug("user_page_offset: %llu", user_page_offset);
size_t user_pages_required = PAGES_REQUIRED(user_size + user_page_offset);
LogDebug("user_pages_required: %llu", user_pages_required);
size_t user_pages_pined = get_user_pages_fast(user_page_addr, user_pages_required, read_only ? 0 : 1, out_user_pages);
LogDebug("user_pages_pined: %llu", user_pages_pined);
if (user_pages_pined < user_pages_required) goto error;
uint8_t *kernel_base = vmap(out_user_pages, user_pages_pined, VM_MAP, PAGE_KERNEL);
LogDebug("kernel_base: %llu", kernel_base);
if (!kernel_base) goto error;
*out_user_pages_maped = user_pages_pined;
*out_kernel_base = kernel_base;
*out_kernel_addr = kernel_base + user_page_offset;
return s_success;
error:
for (size_t i = 0; i < user_pages_pined; ++i) put_page(out_user_pages[i]);
return s_fail;
}
void device_unmap_user_buffer(/*in*/struct page **user_pages,
/*in*/size_t user_pages_maped,
/*in*/uint8_t *kernel_base)
{
vunmap(kernel_base);
for (size_t i = 0; i < user_pages_maped; ++i) put_page(user_pages[i]);
}
status_t device_open(struct inode *pinode, struct file *pfile)
{
int minor;
struct usb_interface *interface;
PDEVICE_CONTEXT DeviceContext;
PFILE_CONTEXT FileContext;
minor = iminor(pinode);
interface = usb_find_interface(&g_driver, minor);
if (!interface) return s_dev;
DeviceContext = usb_get_intfdata(interface);
if (!DeviceContext) return s_dev;
FileContext = heap_alloc(sizeof(*FileContext));
FileContext->context = DeviceContext;
pfile->private_data = FileContext;
return s_success;
}
status_t device_close(struct inode *pinode, struct file *pfile)
{
heap_free(pfile->private_data);
pfile->private_data = NULL;
return s_success;
}
long device_ioctl(struct file *pfile, unsigned int IoControlCode, unsigned long arg)
{
status_t status;
bool sequential;
ERROR_CODE errCode;
size_t szBytestoReturn = 0;
struct user_packet packet;
PFILE_CONTEXT FileContext;
PDEVICE_CONTEXT DeviceContext;
size_t inSize = 0, outSize = 0;
uint8_t *inBuf = NULL, *outBuf = NULL;
size_t inPages = 0, outPages = 0;
uint8_t *inBase = NULL, *outBase = NULL;
FileContext = pfile->private_data;
if (!FileContext) return s_dev;
DeviceContext = FileContext->context;
if (!DeviceContext) return s_dev;
copy_from_user(&packet, (unsigned char*)arg, sizeof(packet));
// for now we don't test if user buffers actually overlap
if (packet.in_size > BUFFER_IN_MAX) packet.in_size = BUFFER_IN_MAX;
if (packet.out_size > BUFFER_OUT_MAX) packet.out_size = BUFFER_OUT_MAX;
bool in_ok = packet.in_buffer && packet.in_size && access_ok(VERIFY_READ, packet.in_buffer, packet.in_size);
bool out_ok = packet.out_buffer && packet.out_size && access_ok(VERIFY_WRITE, packet.out_buffer, packet.out_size);
bool share = in_ok && out_ok && device_user_buffers_share_pages(packet.in_buffer, packet.in_size,
packet.out_buffer, packet.out_size);
if (!share)
{
LogDebug("Not shared region {");
if (in_ok)
{
inSize = packet.in_size;
status = device_map_user_buffer(packet.in_buffer, packet.in_size, true,
FileContext->in_pages, &inPages, &inBase, &inBuf);
if (!status_success(status))
{
LogDebug("device_map_user_buffer failed");
return status;
}
}
if (out_ok)
{
outSize = packet.out_size;
status = device_map_user_buffer(packet.out_buffer, packet.out_size, false,
FileContext->out_pages, &outPages, &outBase, &outBuf);
if (!status_success(status))
{
LogDebug("device_map_user_buffer failed");
return status;
}
}
LogDebug("}");
}
else
{
LogDebug("Shared region {");
uint8_t *begin = (packet.in_buffer < packet.out_buffer) ? (packet.in_buffer) : (packet.out_buffer);
uint8_t *end = ((packet.in_buffer + packet.in_size) < (packet.out_buffer + packet.out_size)) ? (packet.out_buffer + packet.out_size) : (packet.in_buffer + packet.in_size);
inSize = packet.in_size;
outSize = packet.out_size;
status = device_map_user_buffer(begin, end - begin, false,
FileContext->out_pages, &outPages, &outBase, &outBuf);
if (!status_success(status))
{
LogDebug("device_map_user_buffer failed");
return status;
}
if (packet.in_buffer < packet.out_buffer)
{
inBuf = outBuf;
outBuf = inBuf + (packet.out_buffer - packet.in_buffer);
}
else
{
inBuf = outBuf + (packet.in_buffer - packet.out_buffer);
}
LogDebug("}");
}
// do ioctl like this:
// switch (IoControlCode)
// {
// case A:
// errCode = ioctlA(DeviceContext, inBuf, inSize, outBuf, outSize, &szBytestoReturn);
// break;
// }
// if (errCode == ERR_NO_ERROR) status = szBytestoReturn;
// else status = GetSystemStatusByErrCode(errCode);
if (inBase) device_unmap_user_buffer(FileContext->in_pages, inPages, inBase);
if (outBase) device_unmap_user_buffer(FileContext->out_pages, outPages, outBase);
return status;
}
Когда мы выдаем ioctl с одной пользовательской страницей, все работает нормально. Однако, когда мы пытаемся захватить несколько видеокадров, все становится странным, и в итоге мы сталкиваемся с ошибкой сегментации.
Причины, по которым мы не используем функции copy_to_user
, copy_from_user
:
- Нам нужен код, который также работает в Windows.
- 16-битный кадр должен быть преобразован в 8-битный кадр на месте, поэтому этот подход приведет нас к ситуации, когда нам нужно вызывать функцию
put_user
для каждого слова кадра, что будет довольно медленным.
Можете ли вы помочь мне понять, что не так с приведенным выше кодом?