Commit 6ea001f9 authored by Benjamin Gaignard's avatar Benjamin Gaignard Committed by Mauro Carvalho Chehab

media: videobuf2: Access vb2_queue bufs array through helper functions

This patch adds 2 helpers functions to add and remove vb2 buffers
from a queue. With these 2 and vb2_get_buffer(), bufs field of
struct vb2_queue becomes like a private member of the structure.

After each call to vb2_get_buffer() we need to be sure that we get
a valid pointer in preparation for when buffers can be deleted.
Signed-off-by: default avatarBenjamin Gaignard <benjamin.gaignard@collabora.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@kernel.org>
parent 3c147c29
...@@ -413,6 +413,31 @@ static void init_buffer_cache_hints(struct vb2_queue *q, struct vb2_buffer *vb) ...@@ -413,6 +413,31 @@ static void init_buffer_cache_hints(struct vb2_queue *q, struct vb2_buffer *vb)
vb->skip_cache_sync_on_finish = 1; vb->skip_cache_sync_on_finish = 1;
} }
/**
* vb2_queue_add_buffer() - add a buffer to a queue
* @q: pointer to &struct vb2_queue with videobuf2 queue.
* @vb: pointer to &struct vb2_buffer to be added to the queue.
* @index: index where add vb2_buffer in the queue
*/
static void vb2_queue_add_buffer(struct vb2_queue *q, struct vb2_buffer *vb, unsigned int index)
{
WARN_ON(index >= VB2_MAX_FRAME || q->bufs[index] || vb->vb2_queue);
q->bufs[index] = vb;
vb->index = index;
vb->vb2_queue = q;
}
/**
* vb2_queue_remove_buffer() - remove a buffer from a queue
* @vb: pointer to &struct vb2_buffer to be removed from the queue.
*/
static void vb2_queue_remove_buffer(struct vb2_buffer *vb)
{
vb->vb2_queue->bufs[vb->index] = NULL;
vb->vb2_queue = NULL;
}
/* /*
* __vb2_queue_alloc() - allocate vb2 buffer structures and (for MMAP type) * __vb2_queue_alloc() - allocate vb2 buffer structures and (for MMAP type)
* video buffer memory for all buffers/planes on the queue and initializes the * video buffer memory for all buffers/planes on the queue and initializes the
...@@ -441,9 +466,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory, ...@@ -441,9 +466,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
} }
vb->state = VB2_BUF_STATE_DEQUEUED; vb->state = VB2_BUF_STATE_DEQUEUED;
vb->vb2_queue = q;
vb->num_planes = num_planes; vb->num_planes = num_planes;
vb->index = q->num_buffers + buffer;
vb->type = q->type; vb->type = q->type;
vb->memory = memory; vb->memory = memory;
init_buffer_cache_hints(q, vb); init_buffer_cache_hints(q, vb);
...@@ -451,9 +474,9 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory, ...@@ -451,9 +474,9 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
vb->planes[plane].length = plane_sizes[plane]; vb->planes[plane].length = plane_sizes[plane];
vb->planes[plane].min_length = plane_sizes[plane]; vb->planes[plane].min_length = plane_sizes[plane];
} }
call_void_bufop(q, init_buffer, vb);
q->bufs[vb->index] = vb; vb2_queue_add_buffer(q, vb, q->num_buffers + buffer);
call_void_bufop(q, init_buffer, vb);
/* Allocate video buffer memory for the MMAP type */ /* Allocate video buffer memory for the MMAP type */
if (memory == VB2_MEMORY_MMAP) { if (memory == VB2_MEMORY_MMAP) {
...@@ -461,7 +484,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory, ...@@ -461,7 +484,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
if (ret) { if (ret) {
dprintk(q, 1, "failed allocating memory for buffer %d\n", dprintk(q, 1, "failed allocating memory for buffer %d\n",
buffer); buffer);
q->bufs[vb->index] = NULL; vb2_queue_remove_buffer(vb);
kfree(vb); kfree(vb);
break; break;
} }
...@@ -476,7 +499,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory, ...@@ -476,7 +499,7 @@ static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
dprintk(q, 1, "buffer %d %p initialization failed\n", dprintk(q, 1, "buffer %d %p initialization failed\n",
buffer, vb); buffer, vb);
__vb2_buf_mem_free(vb); __vb2_buf_mem_free(vb);
q->bufs[vb->index] = NULL; vb2_queue_remove_buffer(vb);
kfree(vb); kfree(vb);
break; break;
} }
...@@ -499,7 +522,7 @@ static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers) ...@@ -499,7 +522,7 @@ static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers)
for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; for (buffer = q->num_buffers - buffers; buffer < q->num_buffers;
++buffer) { ++buffer) {
vb = q->bufs[buffer]; vb = vb2_get_buffer(q, buffer);
if (!vb) if (!vb)
continue; continue;
...@@ -527,7 +550,7 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) ...@@ -527,7 +550,7 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
/* Call driver-provided cleanup function for each buffer, if provided */ /* Call driver-provided cleanup function for each buffer, if provided */
for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; for (buffer = q->num_buffers - buffers; buffer < q->num_buffers;
++buffer) { ++buffer) {
struct vb2_buffer *vb = q->bufs[buffer]; struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
if (vb && vb->planes[0].mem_priv) if (vb && vb->planes[0].mem_priv)
call_void_vb_qop(vb, buf_cleanup, vb); call_void_vb_qop(vb, buf_cleanup, vb);
...@@ -568,8 +591,13 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) ...@@ -568,8 +591,13 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
q->cnt_unprepare_streaming = 0; q->cnt_unprepare_streaming = 0;
} }
for (buffer = 0; buffer < q->num_buffers; ++buffer) { for (buffer = 0; buffer < q->num_buffers; ++buffer) {
struct vb2_buffer *vb = q->bufs[buffer]; struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
bool unbalanced = vb->cnt_mem_alloc != vb->cnt_mem_put || bool unbalanced;
if (!vb)
continue;
unbalanced = vb->cnt_mem_alloc != vb->cnt_mem_put ||
vb->cnt_mem_prepare != vb->cnt_mem_finish || vb->cnt_mem_prepare != vb->cnt_mem_finish ||
vb->cnt_mem_get_userptr != vb->cnt_mem_put_userptr || vb->cnt_mem_get_userptr != vb->cnt_mem_put_userptr ||
vb->cnt_mem_attach_dmabuf != vb->cnt_mem_detach_dmabuf || vb->cnt_mem_attach_dmabuf != vb->cnt_mem_detach_dmabuf ||
...@@ -616,8 +644,13 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers) ...@@ -616,8 +644,13 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
/* Free vb2 buffers */ /* Free vb2 buffers */
for (buffer = q->num_buffers - buffers; buffer < q->num_buffers; for (buffer = q->num_buffers - buffers; buffer < q->num_buffers;
++buffer) { ++buffer) {
kfree(q->bufs[buffer]); struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
q->bufs[buffer] = NULL;
if (!vb)
continue;
vb2_queue_remove_buffer(vb);
kfree(vb);
} }
q->num_buffers -= buffers; q->num_buffers -= buffers;
...@@ -653,7 +686,12 @@ static bool __buffers_in_use(struct vb2_queue *q) ...@@ -653,7 +686,12 @@ static bool __buffers_in_use(struct vb2_queue *q)
{ {
unsigned int buffer; unsigned int buffer;
for (buffer = 0; buffer < q->num_buffers; ++buffer) { for (buffer = 0; buffer < q->num_buffers; ++buffer) {
if (vb2_buffer_in_use(q, q->bufs[buffer])) struct vb2_buffer *vb = vb2_get_buffer(q, buffer);
if (!vb)
continue;
if (vb2_buffer_in_use(q, vb))
return true; return true;
} }
return false; return false;
...@@ -1638,7 +1676,11 @@ static int vb2_start_streaming(struct vb2_queue *q) ...@@ -1638,7 +1676,11 @@ static int vb2_start_streaming(struct vb2_queue *q)
* correctly return them to vb2. * correctly return them to vb2.
*/ */
for (i = 0; i < q->num_buffers; ++i) { for (i = 0; i < q->num_buffers; ++i) {
vb = q->bufs[i]; vb = vb2_get_buffer(q, i);
if (!vb)
continue;
if (vb->state == VB2_BUF_STATE_ACTIVE) if (vb->state == VB2_BUF_STATE_ACTIVE)
vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED); vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
} }
...@@ -2039,11 +2081,17 @@ static void __vb2_queue_cancel(struct vb2_queue *q) ...@@ -2039,11 +2081,17 @@ static void __vb2_queue_cancel(struct vb2_queue *q)
* to vb2 in stop_streaming(). * to vb2 in stop_streaming().
*/ */
if (WARN_ON(atomic_read(&q->owned_by_drv_count))) { if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
for (i = 0; i < q->num_buffers; ++i) for (i = 0; i < q->num_buffers; ++i) {
if (q->bufs[i]->state == VB2_BUF_STATE_ACTIVE) { struct vb2_buffer *vb = vb2_get_buffer(q, i);
pr_warn("driver bug: stop_streaming operation is leaving buf %p in active state\n",
q->bufs[i]); if (!vb)
vb2_buffer_done(q->bufs[i], VB2_BUF_STATE_ERROR); continue;
if (vb->state == VB2_BUF_STATE_ACTIVE) {
pr_warn("driver bug: stop_streaming operation is leaving buffer %u in active state\n",
vb->index);
vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
}
} }
/* Must be zero now */ /* Must be zero now */
WARN_ON(atomic_read(&q->owned_by_drv_count)); WARN_ON(atomic_read(&q->owned_by_drv_count));
...@@ -2078,9 +2126,14 @@ static void __vb2_queue_cancel(struct vb2_queue *q) ...@@ -2078,9 +2126,14 @@ static void __vb2_queue_cancel(struct vb2_queue *q)
* be changed, so we can't move the buf_finish() to __vb2_dqbuf(). * be changed, so we can't move the buf_finish() to __vb2_dqbuf().
*/ */
for (i = 0; i < q->num_buffers; ++i) { for (i = 0; i < q->num_buffers; ++i) {
struct vb2_buffer *vb = q->bufs[i]; struct vb2_buffer *vb;
struct media_request *req = vb->req_obj.req; struct media_request *req;
vb = vb2_get_buffer(q, i);
if (!vb)
continue;
req = vb->req_obj.req;
/* /*
* If a request is associated with this buffer, then * If a request is associated with this buffer, then
* call buf_request_cancel() to give the driver to complete() * call buf_request_cancel() to give the driver to complete()
...@@ -2229,10 +2282,12 @@ static int __find_plane_by_offset(struct vb2_queue *q, unsigned long offset, ...@@ -2229,10 +2282,12 @@ static int __find_plane_by_offset(struct vb2_queue *q, unsigned long offset,
buffer = (offset >> PLANE_INDEX_SHIFT) & BUFFER_INDEX_MASK; buffer = (offset >> PLANE_INDEX_SHIFT) & BUFFER_INDEX_MASK;
*plane = (offset >> PAGE_SHIFT) & PLANE_INDEX_MASK; *plane = (offset >> PAGE_SHIFT) & PLANE_INDEX_MASK;
if (buffer >= q->num_buffers || *plane >= q->bufs[buffer]->num_planes) *vb = vb2_get_buffer(q, buffer);
if (!*vb)
return -EINVAL;
if (*plane >= (*vb)->num_planes)
return -EINVAL; return -EINVAL;
*vb = q->bufs[buffer];
return 0; return 0;
} }
...@@ -2620,6 +2675,7 @@ struct vb2_fileio_data { ...@@ -2620,6 +2675,7 @@ struct vb2_fileio_data {
static int __vb2_init_fileio(struct vb2_queue *q, int read) static int __vb2_init_fileio(struct vb2_queue *q, int read)
{ {
struct vb2_fileio_data *fileio; struct vb2_fileio_data *fileio;
struct vb2_buffer *vb;
int i, ret; int i, ret;
unsigned int count = 0; unsigned int count = 0;
...@@ -2670,11 +2726,18 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read) ...@@ -2670,11 +2726,18 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
if (ret) if (ret)
goto err_kfree; goto err_kfree;
/*
* Userspace can never add or delete buffers later, so there
* will never be holes. It is safe to assume that vb2_get_buffer(q, 0)
* will always return a valid vb pointer
*/
vb = vb2_get_buffer(q, 0);
/* /*
* Check if plane_count is correct * Check if plane_count is correct
* (multiplane buffers are not supported). * (multiplane buffers are not supported).
*/ */
if (q->bufs[0]->num_planes != 1) { if (vb->num_planes != 1) {
ret = -EBUSY; ret = -EBUSY;
goto err_reqbufs; goto err_reqbufs;
} }
...@@ -2683,12 +2746,15 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read) ...@@ -2683,12 +2746,15 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
* Get kernel address of each buffer. * Get kernel address of each buffer.
*/ */
for (i = 0; i < q->num_buffers; i++) { for (i = 0; i < q->num_buffers; i++) {
fileio->bufs[i].vaddr = vb2_plane_vaddr(q->bufs[i], 0); /* vb can never be NULL when using fileio. */
vb = vb2_get_buffer(q, i);
fileio->bufs[i].vaddr = vb2_plane_vaddr(vb, 0);
if (fileio->bufs[i].vaddr == NULL) { if (fileio->bufs[i].vaddr == NULL) {
ret = -EINVAL; ret = -EINVAL;
goto err_reqbufs; goto err_reqbufs;
} }
fileio->bufs[i].size = vb2_plane_size(q->bufs[i], 0); fileio->bufs[i].size = vb2_plane_size(vb, 0);
} }
/* /*
...@@ -2699,7 +2765,12 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read) ...@@ -2699,7 +2765,12 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
* Queue all buffers. * Queue all buffers.
*/ */
for (i = 0; i < q->num_buffers; i++) { for (i = 0; i < q->num_buffers; i++) {
ret = vb2_core_qbuf(q, q->bufs[i], NULL, NULL); struct vb2_buffer *vb2 = vb2_get_buffer(q, i);
if (!vb2)
continue;
ret = vb2_core_qbuf(q, vb2, NULL, NULL);
if (ret) if (ret)
goto err_reqbufs; goto err_reqbufs;
fileio->bufs[i].queued = 1; fileio->bufs[i].queued = 1;
...@@ -2816,15 +2887,17 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ ...@@ -2816,15 +2887,17 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_
fileio->cur_index = index; fileio->cur_index = index;
buf = &fileio->bufs[index]; buf = &fileio->bufs[index];
b = q->bufs[index];
/* b can never be NULL when using fileio. */
b = vb2_get_buffer(q, index);
/* /*
* Get number of bytes filled by the driver * Get number of bytes filled by the driver
*/ */
buf->pos = 0; buf->pos = 0;
buf->queued = 0; buf->queued = 0;
buf->size = read ? vb2_get_plane_payload(q->bufs[index], 0) buf->size = read ? vb2_get_plane_payload(b, 0)
: vb2_plane_size(q->bufs[index], 0); : vb2_plane_size(b, 0);
/* Compensate for data_offset on read in the multiplanar case. */ /* Compensate for data_offset on read in the multiplanar case. */
if (is_multiplanar && read && if (is_multiplanar && read &&
b->planes[0].data_offset < buf->size) { b->planes[0].data_offset < buf->size) {
...@@ -2867,7 +2940,8 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ ...@@ -2867,7 +2940,8 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_
* Queue next buffer if required. * Queue next buffer if required.
*/ */
if (buf->pos == buf->size || (!read && fileio->write_immediately)) { if (buf->pos == buf->size || (!read && fileio->write_immediately)) {
struct vb2_buffer *b = q->bufs[index]; /* b can never be NULL when using fileio. */
struct vb2_buffer *b = vb2_get_buffer(q, index);
/* /*
* Check if this is the last buffer to read. * Check if this is the last buffer to read.
...@@ -2894,7 +2968,7 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ ...@@ -2894,7 +2968,7 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_
*/ */
buf->pos = 0; buf->pos = 0;
buf->queued = 1; buf->queued = 1;
buf->size = vb2_plane_size(q->bufs[index], 0); buf->size = vb2_plane_size(b, 0);
fileio->q_count += 1; fileio->q_count += 1;
/* /*
* If we are queuing up buffers for the first time, then * If we are queuing up buffers for the first time, then
...@@ -2965,7 +3039,9 @@ static int vb2_thread(void *data) ...@@ -2965,7 +3039,9 @@ static int vb2_thread(void *data)
* Call vb2_dqbuf to get buffer back. * Call vb2_dqbuf to get buffer back.
*/ */
if (prequeue) { if (prequeue) {
vb = q->bufs[index++]; vb = vb2_get_buffer(q, index++);
if (!vb)
continue;
prequeue--; prequeue--;
} else { } else {
call_void_qop(q, wait_finish, q); call_void_qop(q, wait_finish, q);
...@@ -2974,7 +3050,7 @@ static int vb2_thread(void *data) ...@@ -2974,7 +3050,7 @@ static int vb2_thread(void *data)
call_void_qop(q, wait_prepare, q); call_void_qop(q, wait_prepare, q);
dprintk(q, 5, "file io: vb2_dqbuf result: %d\n", ret); dprintk(q, 5, "file io: vb2_dqbuf result: %d\n", ret);
if (!ret) if (!ret)
vb = q->bufs[index]; vb = vb2_get_buffer(q, index);
} }
if (ret || threadio->stop) if (ret || threadio->stop)
break; break;
......
...@@ -615,11 +615,22 @@ static const struct vb2_buf_ops v4l2_buf_ops = { ...@@ -615,11 +615,22 @@ static const struct vb2_buf_ops v4l2_buf_ops = {
struct vb2_buffer *vb2_find_buffer(struct vb2_queue *q, u64 timestamp) struct vb2_buffer *vb2_find_buffer(struct vb2_queue *q, u64 timestamp)
{ {
unsigned int i; unsigned int i;
struct vb2_buffer *vb2;
for (i = 0; i < q->num_buffers; i++) /*
if (q->bufs[i]->copied_timestamp && * This loop doesn't scale if there is a really large number of buffers.
q->bufs[i]->timestamp == timestamp) * Maybe something more efficient will be needed in this case.
return vb2_get_buffer(q, i); */
for (i = 0; i < q->num_buffers; i++) {
vb2 = vb2_get_buffer(q, i);
if (!vb2)
continue;
if (vb2->copied_timestamp &&
vb2->timestamp == timestamp)
return vb2;
}
return NULL; return NULL;
} }
EXPORT_SYMBOL_GPL(vb2_find_buffer); EXPORT_SYMBOL_GPL(vb2_find_buffer);
...@@ -647,11 +658,12 @@ int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b) ...@@ -647,11 +658,12 @@ int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
return -EINVAL; return -EINVAL;
} }
if (b->index >= q->num_buffers) { vb = vb2_get_buffer(q, b->index);
dprintk(q, 1, "buffer index out of range\n"); if (!vb) {
dprintk(q, 1, "can't find the requested buffer %u\n", b->index);
return -EINVAL; return -EINVAL;
} }
vb = q->bufs[b->index];
ret = __verify_planes_array(vb, b); ret = __verify_planes_array(vb, b);
if (!ret) if (!ret)
vb2_core_querybuf(q, vb, b); vb2_core_querybuf(q, vb, b);
...@@ -721,11 +733,11 @@ int vb2_prepare_buf(struct vb2_queue *q, struct media_device *mdev, ...@@ -721,11 +733,11 @@ int vb2_prepare_buf(struct vb2_queue *q, struct media_device *mdev,
if (b->flags & V4L2_BUF_FLAG_REQUEST_FD) if (b->flags & V4L2_BUF_FLAG_REQUEST_FD)
return -EINVAL; return -EINVAL;
if (b->index >= q->num_buffers) { vb = vb2_get_buffer(q, b->index);
dprintk(q, 1, "buffer index out of range\n"); if (!vb) {
dprintk(q, 1, "can't find the requested buffer %u\n", b->index);
return -EINVAL; return -EINVAL;
} }
vb = q->bufs[b->index];
ret = vb2_queue_or_prepare_buf(q, mdev, vb, b, true, NULL); ret = vb2_queue_or_prepare_buf(q, mdev, vb, b, true, NULL);
...@@ -809,7 +821,11 @@ int vb2_qbuf(struct vb2_queue *q, struct media_device *mdev, ...@@ -809,7 +821,11 @@ int vb2_qbuf(struct vb2_queue *q, struct media_device *mdev,
dprintk(q, 1, "buffer index out of range\n"); dprintk(q, 1, "buffer index out of range\n");
return -EINVAL; return -EINVAL;
} }
vb = q->bufs[b->index]; vb = vb2_get_buffer(q, b->index);
if (!vb) {
dprintk(q, 1, "can't find the requested buffer %u\n", b->index);
return -EINVAL;
}
ret = vb2_queue_or_prepare_buf(q, mdev, vb, b, false, &req); ret = vb2_queue_or_prepare_buf(q, mdev, vb, b, false, &req);
if (ret) if (ret)
...@@ -880,7 +896,11 @@ int vb2_expbuf(struct vb2_queue *q, struct v4l2_exportbuffer *eb) ...@@ -880,7 +896,11 @@ int vb2_expbuf(struct vb2_queue *q, struct v4l2_exportbuffer *eb)
dprintk(q, 1, "buffer index out of range\n"); dprintk(q, 1, "buffer index out of range\n");
return -EINVAL; return -EINVAL;
} }
vb = q->bufs[eb->index]; vb = vb2_get_buffer(q, eb->index);
if (!vb) {
dprintk(q, 1, "can't find the requested buffer %u\n", eb->index);
return -EINVAL;
}
return vb2_core_expbuf(q, &eb->fd, eb->type, vb, return vb2_core_expbuf(q, &eb->fd, eb->type, vb,
eb->plane, eb->flags); eb->plane, eb->flags);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment