diff options
| author | Hans Verkuil <hverkuil@xs4all.nl> | 2016-01-27 07:08:42 -0500 |
|---|---|---|
| committer | Mauro Carvalho Chehab <mchehab@osg.samsung.com> | 2016-02-04 06:13:46 -0500 |
| commit | fac710e45d0b12443acd4d905c9acec27ab4ca14 (patch) | |
| tree | f2c04c4a2e2fad5dae41bcb05e015a4ea6df2f7e /drivers | |
| parent | e8beb02343e7582980c6705816cd957cf4f74c7a (diff) | |
[media] vb2: fix nasty vb2_thread regression
The vb2_thread implementation was made generic and was moved from
videobuf2-v4l2.c to videobuf2-core.c in commit af3bac1a. Unfortunately
that clearly was never tested since it broke read() causing NULL address
references.
The root cause was confused handling of vb2_buffer vs v4l2_buffer (the pb
pointer in various core functions).
The v4l2_buffer no longer exists after moving the code into the core and
it is no longer needed. However, the vb2_thread code passed a pointer to
a vb2_buffer to the core functions were a v4l2_buffer pointer was expected
and vb2_thread expected that the vb2_buffer fields would be filled in
correctly.
This is obviously wrong since v4l2_buffer != vb2_buffer. Note that the
pb pointer is a void pointer, so no type-checking took place.
This patch fixes this problem:
1) allow pb to be NULL for vb2_core_(d)qbuf. The vb2_thread code will use
a NULL pointer here since they don't care about v4l2_buffer anyway.
2) let vb2_core_dqbuf pass back the index of the received buffer. This is
all vb2_thread needs: this index is the index into the q->bufs array
and vb2_thread just gets the vb2_buffer from there.
3) the fileio->b pointer (that originally contained a v4l2_buffer) is
removed altogether since it is no longer needed.
Tested with vivid and the cobalt driver.
Cc: stable@vger.kernel.org # Kernel >= 4.3
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Reported-by: Matthias Schwarzott <zzam@gentoo.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/media/v4l2-core/videobuf2-core.c | 95 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/videobuf2-v4l2.c | 2 |
2 files changed, 44 insertions, 53 deletions
diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c index c5d49d7a0d76..b53e42c329cb 100644 --- a/drivers/media/v4l2-core/videobuf2-core.c +++ b/drivers/media/v4l2-core/videobuf2-core.c | |||
| @@ -1063,8 +1063,11 @@ EXPORT_SYMBOL_GPL(vb2_discard_done); | |||
| 1063 | */ | 1063 | */ |
| 1064 | static int __qbuf_mmap(struct vb2_buffer *vb, const void *pb) | 1064 | static int __qbuf_mmap(struct vb2_buffer *vb, const void *pb) |
| 1065 | { | 1065 | { |
| 1066 | int ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, | 1066 | int ret = 0; |
| 1067 | vb, pb, vb->planes); | 1067 | |
| 1068 | if (pb) | ||
| 1069 | ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, | ||
| 1070 | vb, pb, vb->planes); | ||
| 1068 | return ret ? ret : call_vb_qop(vb, buf_prepare, vb); | 1071 | return ret ? ret : call_vb_qop(vb, buf_prepare, vb); |
| 1069 | } | 1072 | } |
| 1070 | 1073 | ||
| @@ -1077,14 +1080,16 @@ static int __qbuf_userptr(struct vb2_buffer *vb, const void *pb) | |||
| 1077 | struct vb2_queue *q = vb->vb2_queue; | 1080 | struct vb2_queue *q = vb->vb2_queue; |
| 1078 | void *mem_priv; | 1081 | void *mem_priv; |
| 1079 | unsigned int plane; | 1082 | unsigned int plane; |
| 1080 | int ret; | 1083 | int ret = 0; |
| 1081 | enum dma_data_direction dma_dir = | 1084 | enum dma_data_direction dma_dir = |
| 1082 | q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE; | 1085 | q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE; |
| 1083 | bool reacquired = vb->planes[0].mem_priv == NULL; | 1086 | bool reacquired = vb->planes[0].mem_priv == NULL; |
| 1084 | 1087 | ||
| 1085 | memset(planes, 0, sizeof(planes[0]) * vb->num_planes); | 1088 | memset(planes, 0, sizeof(planes[0]) * vb->num_planes); |
| 1086 | /* Copy relevant information provided by the userspace */ | 1089 | /* Copy relevant information provided by the userspace */ |
| 1087 | ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, pb, planes); | 1090 | if (pb) |
| 1091 | ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, | ||
| 1092 | vb, pb, planes); | ||
| 1088 | if (ret) | 1093 | if (ret) |
| 1089 | return ret; | 1094 | return ret; |
| 1090 | 1095 | ||
| @@ -1192,14 +1197,16 @@ static int __qbuf_dmabuf(struct vb2_buffer *vb, const void *pb) | |||
| 1192 | struct vb2_queue *q = vb->vb2_queue; | 1197 | struct vb2_queue *q = vb->vb2_queue; |
| 1193 | void *mem_priv; | 1198 | void *mem_priv; |
| 1194 | unsigned int plane; | 1199 | unsigned int plane; |
| 1195 | int ret; | 1200 | int ret = 0; |
| 1196 | enum dma_data_direction dma_dir = | 1201 | enum dma_data_direction dma_dir = |
| 1197 | q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE; | 1202 | q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE; |
| 1198 | bool reacquired = vb->planes[0].mem_priv == NULL; | 1203 | bool reacquired = vb->planes[0].mem_priv == NULL; |
| 1199 | 1204 | ||
| 1200 | memset(planes, 0, sizeof(planes[0]) * vb->num_planes); | 1205 | memset(planes, 0, sizeof(planes[0]) * vb->num_planes); |
| 1201 | /* Copy relevant information provided by the userspace */ | 1206 | /* Copy relevant information provided by the userspace */ |
| 1202 | ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, pb, planes); | 1207 | if (pb) |
| 1208 | ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, | ||
| 1209 | vb, pb, planes); | ||
| 1203 | if (ret) | 1210 | if (ret) |
| 1204 | return ret; | 1211 | return ret; |
| 1205 | 1212 | ||
| @@ -1520,7 +1527,8 @@ int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb) | |||
| 1520 | q->waiting_for_buffers = false; | 1527 | q->waiting_for_buffers = false; |
| 1521 | vb->state = VB2_BUF_STATE_QUEUED; | 1528 | vb->state = VB2_BUF_STATE_QUEUED; |
| 1522 | 1529 | ||
| 1523 | call_void_bufop(q, copy_timestamp, vb, pb); | 1530 | if (pb) |
| 1531 | call_void_bufop(q, copy_timestamp, vb, pb); | ||
| 1524 | 1532 | ||
| 1525 | trace_vb2_qbuf(q, vb); | 1533 | trace_vb2_qbuf(q, vb); |
| 1526 | 1534 | ||
| @@ -1532,7 +1540,8 @@ int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb) | |||
| 1532 | __enqueue_in_driver(vb); | 1540 | __enqueue_in_driver(vb); |
| 1533 | 1541 | ||
| 1534 | /* Fill buffer information for the userspace */ | 1542 | /* Fill buffer information for the userspace */ |
| 1535 | call_void_bufop(q, fill_user_buffer, vb, pb); | 1543 | if (pb) |
| 1544 | call_void_bufop(q, fill_user_buffer, vb, pb); | ||
| 1536 | 1545 | ||
| 1537 | /* | 1546 | /* |
| 1538 | * If streamon has been called, and we haven't yet called | 1547 | * If streamon has been called, and we haven't yet called |
| @@ -1731,7 +1740,8 @@ static void __vb2_dqbuf(struct vb2_buffer *vb) | |||
| 1731 | * The return values from this function are intended to be directly returned | 1740 | * The return values from this function are intended to be directly returned |
| 1732 | * from vidioc_dqbuf handler in driver. | 1741 | * from vidioc_dqbuf handler in driver. |
| 1733 | */ | 1742 | */ |
| 1734 | int vb2_core_dqbuf(struct vb2_queue *q, void *pb, bool nonblocking) | 1743 | int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb, |
| 1744 | bool nonblocking) | ||
| 1735 | { | 1745 | { |
| 1736 | struct vb2_buffer *vb = NULL; | 1746 | struct vb2_buffer *vb = NULL; |
| 1737 | int ret; | 1747 | int ret; |
| @@ -1754,8 +1764,12 @@ int vb2_core_dqbuf(struct vb2_queue *q, void *pb, bool nonblocking) | |||
| 1754 | 1764 | ||
| 1755 | call_void_vb_qop(vb, buf_finish, vb); | 1765 | call_void_vb_qop(vb, buf_finish, vb); |
| 1756 | 1766 | ||
| 1767 | if (pindex) | ||
| 1768 | *pindex = vb->index; | ||
| 1769 | |||
| 1757 | /* Fill buffer information for the userspace */ | 1770 | /* Fill buffer information for the userspace */ |
| 1758 | call_void_bufop(q, fill_user_buffer, vb, pb); | 1771 | if (pb) |
| 1772 | call_void_bufop(q, fill_user_buffer, vb, pb); | ||
| 1759 | 1773 | ||
| 1760 | /* Remove from videobuf queue */ | 1774 | /* Remove from videobuf queue */ |
| 1761 | list_del(&vb->queued_entry); | 1775 | list_del(&vb->queued_entry); |
| @@ -1828,7 +1842,7 @@ static void __vb2_queue_cancel(struct vb2_queue *q) | |||
| 1828 | * that's done in dqbuf, but that's not going to happen when we | 1842 | * that's done in dqbuf, but that's not going to happen when we |
| 1829 | * cancel the whole queue. Note: this code belongs here, not in | 1843 | * cancel the whole queue. Note: this code belongs here, not in |
| 1830 | * __vb2_dqbuf() since in vb2_internal_dqbuf() there is a critical | 1844 | * __vb2_dqbuf() since in vb2_internal_dqbuf() there is a critical |
| 1831 | * call to __fill_v4l2_buffer() after buf_finish(). That order can't | 1845 | * call to __fill_user_buffer() after buf_finish(). That order can't |
| 1832 | * be changed, so we can't move the buf_finish() to __vb2_dqbuf(). | 1846 | * be changed, so we can't move the buf_finish() to __vb2_dqbuf(). |
| 1833 | */ | 1847 | */ |
| 1834 | for (i = 0; i < q->num_buffers; ++i) { | 1848 | for (i = 0; i < q->num_buffers; ++i) { |
| @@ -2357,7 +2371,6 @@ struct vb2_fileio_data { | |||
| 2357 | unsigned int count; | 2371 | unsigned int count; |
| 2358 | unsigned int type; | 2372 | unsigned int type; |
| 2359 | unsigned int memory; | 2373 | unsigned int memory; |
| 2360 | struct vb2_buffer *b; | ||
| 2361 | struct vb2_fileio_buf bufs[VB2_MAX_FRAME]; | 2374 | struct vb2_fileio_buf bufs[VB2_MAX_FRAME]; |
| 2362 | unsigned int cur_index; | 2375 | unsigned int cur_index; |
| 2363 | unsigned int initial_index; | 2376 | unsigned int initial_index; |
| @@ -2410,12 +2423,6 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read) | |||
| 2410 | if (fileio == NULL) | 2423 | if (fileio == NULL) |
| 2411 | return -ENOMEM; | 2424 | return -ENOMEM; |
| 2412 | 2425 | ||
| 2413 | fileio->b = kzalloc(q->buf_struct_size, GFP_KERNEL); | ||
| 2414 | if (fileio->b == NULL) { | ||
| 2415 | kfree(fileio); | ||
| 2416 | return -ENOMEM; | ||
| 2417 | } | ||
| 2418 | |||
| 2419 | fileio->read_once = q->fileio_read_once; | 2426 | fileio->read_once = q->fileio_read_once; |
| 2420 | fileio->write_immediately = q->fileio_write_immediately; | 2427 | fileio->write_immediately = q->fileio_write_immediately; |
| 2421 | 2428 | ||
| @@ -2460,13 +2467,7 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read) | |||
| 2460 | * Queue all buffers. | 2467 | * Queue all buffers. |
| 2461 | */ | 2468 | */ |
| 2462 | for (i = 0; i < q->num_buffers; i++) { | 2469 | for (i = 0; i < q->num_buffers; i++) { |
| 2463 | struct vb2_buffer *b = fileio->b; | 2470 | ret = vb2_core_qbuf(q, i, NULL); |
| 2464 | |||
| 2465 | memset(b, 0, q->buf_struct_size); | ||
| 2466 | b->type = q->type; | ||
| 2467 | b->memory = q->memory; | ||
| 2468 | b->index = i; | ||
| 2469 | ret = vb2_core_qbuf(q, i, b); | ||
| 2470 | if (ret) | 2471 | if (ret) |
| 2471 | goto err_reqbufs; | 2472 | goto err_reqbufs; |
| 2472 | fileio->bufs[i].queued = 1; | 2473 | fileio->bufs[i].queued = 1; |
| @@ -2511,7 +2512,6 @@ static int __vb2_cleanup_fileio(struct vb2_queue *q) | |||
| 2511 | q->fileio = NULL; | 2512 | q->fileio = NULL; |
| 2512 | fileio->count = 0; | 2513 | fileio->count = 0; |
| 2513 | vb2_core_reqbufs(q, fileio->memory, &fileio->count); | 2514 | vb2_core_reqbufs(q, fileio->memory, &fileio->count); |
| 2514 | kfree(fileio->b); | ||
| 2515 | kfree(fileio); | 2515 | kfree(fileio); |
| 2516 | dprintk(3, "file io emulator closed\n"); | 2516 | dprintk(3, "file io emulator closed\n"); |
| 2517 | } | 2517 | } |
| @@ -2539,7 +2539,8 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ | |||
| 2539 | * else is able to provide this information with the write() operation. | 2539 | * else is able to provide this information with the write() operation. |
| 2540 | */ | 2540 | */ |
| 2541 | bool copy_timestamp = !read && q->copy_timestamp; | 2541 | bool copy_timestamp = !read && q->copy_timestamp; |
| 2542 | int ret, index; | 2542 | unsigned index; |
| 2543 | int ret; | ||
| 2543 | 2544 | ||
| 2544 | dprintk(3, "mode %s, offset %ld, count %zd, %sblocking\n", | 2545 | dprintk(3, "mode %s, offset %ld, count %zd, %sblocking\n", |
| 2545 | read ? "read" : "write", (long)*ppos, count, | 2546 | read ? "read" : "write", (long)*ppos, count, |
| @@ -2564,22 +2565,20 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ | |||
| 2564 | */ | 2565 | */ |
| 2565 | index = fileio->cur_index; | 2566 | index = fileio->cur_index; |
| 2566 | if (index >= q->num_buffers) { | 2567 | if (index >= q->num_buffers) { |
| 2567 | struct vb2_buffer *b = fileio->b; | 2568 | struct vb2_buffer *b; |
| 2568 | 2569 | ||
| 2569 | /* | 2570 | /* |
| 2570 | * Call vb2_dqbuf to get buffer back. | 2571 | * Call vb2_dqbuf to get buffer back. |
| 2571 | */ | 2572 | */ |
| 2572 | memset(b, 0, q->buf_struct_size); | 2573 | ret = vb2_core_dqbuf(q, &index, NULL, nonblock); |
| 2573 | b->type = q->type; | ||
| 2574 | b->memory = q->memory; | ||
| 2575 | ret = vb2_core_dqbuf(q, b, nonblock); | ||
| 2576 | dprintk(5, "vb2_dqbuf result: %d\n", ret); | 2574 | dprintk(5, "vb2_dqbuf result: %d\n", ret); |
| 2577 | if (ret) | 2575 | if (ret) |
| 2578 | return ret; | 2576 | return ret; |
| 2579 | fileio->dq_count += 1; | 2577 | fileio->dq_count += 1; |
| 2580 | 2578 | ||
| 2581 | fileio->cur_index = index = b->index; | 2579 | fileio->cur_index = index; |
| 2582 | buf = &fileio->bufs[index]; | 2580 | buf = &fileio->bufs[index]; |
| 2581 | b = q->bufs[index]; | ||
| 2583 | 2582 | ||
| 2584 | /* | 2583 | /* |
| 2585 | * Get number of bytes filled by the driver | 2584 | * Get number of bytes filled by the driver |
| @@ -2630,7 +2629,7 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ | |||
| 2630 | * Queue next buffer if required. | 2629 | * Queue next buffer if required. |
| 2631 | */ | 2630 | */ |
| 2632 | if (buf->pos == buf->size || (!read && fileio->write_immediately)) { | 2631 | if (buf->pos == buf->size || (!read && fileio->write_immediately)) { |
| 2633 | struct vb2_buffer *b = fileio->b; | 2632 | struct vb2_buffer *b = q->bufs[index]; |
| 2634 | 2633 | ||
| 2635 | /* | 2634 | /* |
| 2636 | * Check if this is the last buffer to read. | 2635 | * Check if this is the last buffer to read. |
| @@ -2643,15 +2642,11 @@ static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_ | |||
| 2643 | /* | 2642 | /* |
| 2644 | * Call vb2_qbuf and give buffer to the driver. | 2643 | * Call vb2_qbuf and give buffer to the driver. |
| 2645 | */ | 2644 | */ |
| 2646 | memset(b, 0, q->buf_struct_size); | ||
| 2647 | b->type = q->type; | ||
| 2648 | b->memory = q->memory; | ||
| 2649 | b->index = index; | ||
| 2650 | b->planes[0].bytesused = buf->pos; | 2645 | b->planes[0].bytesused = buf->pos; |
| 2651 | 2646 | ||
| 2652 | if (copy_timestamp) | 2647 | if (copy_timestamp) |
| 2653 | b->timestamp = ktime_get_ns(); | 2648 | b->timestamp = ktime_get_ns(); |
| 2654 | ret = vb2_core_qbuf(q, index, b); | 2649 | ret = vb2_core_qbuf(q, index, NULL); |
| 2655 | dprintk(5, "vb2_dbuf result: %d\n", ret); | 2650 | dprintk(5, "vb2_dbuf result: %d\n", ret); |
| 2656 | if (ret) | 2651 | if (ret) |
| 2657 | return ret; | 2652 | return ret; |
| @@ -2713,10 +2708,9 @@ static int vb2_thread(void *data) | |||
| 2713 | { | 2708 | { |
| 2714 | struct vb2_queue *q = data; | 2709 | struct vb2_queue *q = data; |
| 2715 | struct vb2_threadio_data *threadio = q->threadio; | 2710 | struct vb2_threadio_data *threadio = q->threadio; |
| 2716 | struct vb2_fileio_data *fileio = q->fileio; | ||
| 2717 | bool copy_timestamp = false; | 2711 | bool copy_timestamp = false; |
| 2718 | int prequeue = 0; | 2712 | unsigned prequeue = 0; |
| 2719 | int index = 0; | 2713 | unsigned index = 0; |
| 2720 | int ret = 0; | 2714 | int ret = 0; |
| 2721 | 2715 | ||
| 2722 | if (q->is_output) { | 2716 | if (q->is_output) { |
| @@ -2728,37 +2722,34 @@ static int vb2_thread(void *data) | |||
| 2728 | 2722 | ||
| 2729 | for (;;) { | 2723 | for (;;) { |
| 2730 | struct vb2_buffer *vb; | 2724 | struct vb2_buffer *vb; |
| 2731 | struct vb2_buffer *b = fileio->b; | ||
| 2732 | 2725 | ||
| 2733 | /* | 2726 | /* |
| 2734 | * Call vb2_dqbuf to get buffer back. | 2727 | * Call vb2_dqbuf to get buffer back. |
| 2735 | */ | 2728 | */ |
| 2736 | memset(b, 0, q->buf_struct_size); | ||
| 2737 | b->type = q->type; | ||
| 2738 | b->memory = q->memory; | ||
| 2739 | if (prequeue) { | 2729 | if (prequeue) { |
| 2740 | b->index = index++; | 2730 | vb = q->bufs[index++]; |
| 2741 | prequeue--; | 2731 | prequeue--; |
| 2742 | } else { | 2732 | } else { |
| 2743 | call_void_qop(q, wait_finish, q); | 2733 | call_void_qop(q, wait_finish, q); |
| 2744 | if (!threadio->stop) | 2734 | if (!threadio->stop) |
| 2745 | ret = vb2_core_dqbuf(q, b, 0); | 2735 | ret = vb2_core_dqbuf(q, &index, NULL, 0); |
| 2746 | call_void_qop(q, wait_prepare, q); | 2736 | call_void_qop(q, wait_prepare, q); |
| 2747 | dprintk(5, "file io: vb2_dqbuf result: %d\n", ret); | 2737 | dprintk(5, "file io: vb2_dqbuf result: %d\n", ret); |
| 2738 | if (!ret) | ||
| 2739 | vb = q->bufs[index]; | ||
| 2748 | } | 2740 | } |
| 2749 | if (ret || threadio->stop) | 2741 | if (ret || threadio->stop) |
| 2750 | break; | 2742 | break; |
| 2751 | try_to_freeze(); | 2743 | try_to_freeze(); |
| 2752 | 2744 | ||
| 2753 | vb = q->bufs[b->index]; | 2745 | if (vb->state == VB2_BUF_STATE_DONE) |
| 2754 | if (b->state == VB2_BUF_STATE_DONE) | ||
| 2755 | if (threadio->fnc(vb, threadio->priv)) | 2746 | if (threadio->fnc(vb, threadio->priv)) |
| 2756 | break; | 2747 | break; |
| 2757 | call_void_qop(q, wait_finish, q); | 2748 | call_void_qop(q, wait_finish, q); |
| 2758 | if (copy_timestamp) | 2749 | if (copy_timestamp) |
| 2759 | b->timestamp = ktime_get_ns();; | 2750 | vb->timestamp = ktime_get_ns();; |
| 2760 | if (!threadio->stop) | 2751 | if (!threadio->stop) |
| 2761 | ret = vb2_core_qbuf(q, b->index, b); | 2752 | ret = vb2_core_qbuf(q, vb->index, NULL); |
| 2762 | call_void_qop(q, wait_prepare, q); | 2753 | call_void_qop(q, wait_prepare, q); |
| 2763 | if (ret || threadio->stop) | 2754 | if (ret || threadio->stop) |
| 2764 | break; | 2755 | break; |
diff --git a/drivers/media/v4l2-core/videobuf2-v4l2.c b/drivers/media/v4l2-core/videobuf2-v4l2.c index c9a28605511a..91f552124050 100644 --- a/drivers/media/v4l2-core/videobuf2-v4l2.c +++ b/drivers/media/v4l2-core/videobuf2-v4l2.c | |||
| @@ -625,7 +625,7 @@ static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, | |||
| 625 | return -EINVAL; | 625 | return -EINVAL; |
| 626 | } | 626 | } |
| 627 | 627 | ||
| 628 | ret = vb2_core_dqbuf(q, b, nonblocking); | 628 | ret = vb2_core_dqbuf(q, NULL, b, nonblocking); |
| 629 | 629 | ||
| 630 | return ret; | 630 | return ret; |
| 631 | } | 631 | } |
