uvc-gadget: Do not send Set Interface (alternate setting) response twice
[uvc-gadget.git] / uvc-gadget.c
1 /*
2  * UVC gadget test application
3  *
4  * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  */
19
20 #include <sys/time.h>
21 #include <sys/ioctl.h>
22 #include <sys/mman.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/select.h>
26
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #include <linux/usb/ch9.h>
36 #include <linux/usb/video.h>
37 #include <linux/videodev2.h>
38
39 #include "../drivers/usb/gadget/uvc.h"
40
41 #define clamp(val, min, max) ({                 \
42         typeof(val) __val = (val);              \
43         typeof(min) __min = (min);              \
44         typeof(max) __max = (max);              \
45         (void) (&__val == &__min);              \
46         (void) (&__val == &__max);              \
47         __val = __val < __min ? __min: __val;   \
48         __val > __max ? __max: __val; })
49
50 #define ARRAY_SIZE(a)   ((sizeof(a) / sizeof(a[0])))
51
52 struct uvc_device
53 {
54         int fd;
55
56         struct uvc_streaming_control probe;
57         struct uvc_streaming_control commit;
58
59         int control;
60
61         unsigned int fcc;
62         unsigned int width;
63         unsigned int height;
64
65         void **mem;
66         unsigned int nbufs;
67         unsigned int bufsize;
68
69         unsigned int bulk;
70         uint8_t color;
71         unsigned int imgsize;
72         void *imgdata;
73 };
74
75 static struct uvc_device *
76 uvc_open(const char *devname)
77 {
78         struct uvc_device *dev;
79         struct v4l2_capability cap;
80         int ret;
81         int fd;
82
83         fd = open(devname, O_RDWR | O_NONBLOCK);
84         if (fd == -1) {
85                 printf("v4l2 open failed: %s (%d)\n", strerror(errno), errno);
86                 return NULL;
87         }
88
89         printf("open succeeded, file descriptor = %d\n", fd);
90
91         ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
92         if (ret < 0) {
93                 printf("unable to query device: %s (%d)\n", strerror(errno),
94                         errno);
95                 close(fd);
96                 return NULL;
97         }
98
99         printf("device is %s on bus %s\n", cap.card, cap.bus_info);
100
101         dev = malloc(sizeof *dev);
102         if (dev == NULL) {
103                 close(fd);
104                 return NULL;
105         }
106
107         memset(dev, 0, sizeof *dev);
108         dev->fd = fd;
109
110         return dev;
111 }
112
113 static void
114 uvc_close(struct uvc_device *dev)
115 {
116         close(dev->fd);
117         free(dev->imgdata);
118         free(dev->mem);
119         free(dev);
120 }
121
122 /* ---------------------------------------------------------------------------
123  * Video streaming
124  */
125
126 static void
127 uvc_video_fill_buffer(struct uvc_device *dev, struct v4l2_buffer *buf)
128 {
129         unsigned int bpl;
130         unsigned int i;
131
132         switch (dev->fcc) {
133         case V4L2_PIX_FMT_YUYV:
134                 /* Fill the buffer with video data. */
135                 bpl = dev->width * 2;
136                 for (i = 0; i < dev->height; ++i)
137                         memset(dev->mem[buf->index] + i*bpl, dev->color++, bpl);
138
139                 buf->bytesused = bpl * dev->height;
140                 break;
141
142         case V4L2_PIX_FMT_MJPEG:
143                 memcpy(dev->mem[buf->index], dev->imgdata, dev->imgsize);
144                 buf->bytesused = dev->imgsize;
145                 break;
146         }
147 }
148
149 static int
150 uvc_video_process(struct uvc_device *dev)
151 {
152         struct v4l2_buffer buf;
153         int ret;
154
155         memset(&buf, 0, sizeof buf);
156         buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
157         buf.memory = V4L2_MEMORY_MMAP;
158
159         if ((ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf)) < 0) {
160                 printf("Unable to dequeue buffer: %s (%d).\n", strerror(errno),
161                         errno);
162                 return ret;
163         }
164
165         uvc_video_fill_buffer(dev, &buf);
166
167         if ((ret = ioctl(dev->fd, VIDIOC_QBUF, &buf)) < 0) {
168                 printf("Unable to requeue buffer: %s (%d).\n", strerror(errno),
169                         errno);
170                 return ret;
171         }
172
173         return 0;
174 }
175
176 static int
177 uvc_video_reqbufs(struct uvc_device *dev, int nbufs)
178 {
179         struct v4l2_requestbuffers rb;
180         struct v4l2_buffer buf;
181         unsigned int i;
182         int ret;
183
184         for (i = 0; i < dev->nbufs; ++i)
185                 munmap(dev->mem[i], dev->bufsize);
186
187         free(dev->mem);
188         dev->mem = 0;
189         dev->nbufs = 0;
190
191         memset(&rb, 0, sizeof rb);
192         rb.count = nbufs;
193         rb.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
194         rb.memory = V4L2_MEMORY_MMAP;
195
196         ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb);
197         if (ret < 0) {
198                 printf("Unable to allocate buffers: %s (%d).\n",
199                         strerror(errno), errno);
200                 return ret;
201         }
202
203         printf("%u buffers allocated.\n", rb.count);
204
205         /* Map the buffers. */
206         dev->mem = malloc(rb.count * sizeof dev->mem[0]);
207
208         for (i = 0; i < rb.count; ++i) {
209                 memset(&buf, 0, sizeof buf);
210                 buf.index = i;
211                 buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
212                 buf.memory = V4L2_MEMORY_MMAP;
213                 ret = ioctl(dev->fd, VIDIOC_QUERYBUF, &buf);
214                 if (ret < 0) {
215                         printf("Unable to query buffer %u: %s (%d).\n", i,
216                                 strerror(errno), errno);
217                         return -1;
218                 }
219                 printf("length: %u offset: %u\n", buf.length, buf.m.offset);
220
221                 dev->mem[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, dev->fd, buf.m.offset);
222                 if (dev->mem[i] == MAP_FAILED) {
223                         printf("Unable to map buffer %u: %s (%d)\n", i,
224                                 strerror(errno), errno);
225                         return -1;
226                 }
227                 printf("Buffer %u mapped at address %p.\n", i, dev->mem[i]);
228         }
229
230         dev->bufsize = buf.length;
231         dev->nbufs = rb.count;
232
233         return 0;
234 }
235
236 static int
237 uvc_video_stream(struct uvc_device *dev, int enable)
238 {
239         struct v4l2_buffer buf;
240         unsigned int i;
241         int type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
242         int ret;
243
244         if (!enable) {
245                 printf("Stopping video stream.\n");
246                 ioctl(dev->fd, VIDIOC_STREAMOFF, &type);
247                 return 0;
248         }
249
250         printf("Starting video stream.\n");
251
252         for (i = 0; i < dev->nbufs; ++i) {
253                 memset(&buf, 0, sizeof buf);
254
255                 buf.index = i;
256                 buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
257                 buf.memory = V4L2_MEMORY_MMAP;
258
259                 uvc_video_fill_buffer(dev, &buf);
260
261                 printf("Queueing buffer %u.\n", i);
262                 if ((ret = ioctl(dev->fd, VIDIOC_QBUF, &buf)) < 0) {
263                         printf("Unable to queue buffer: %s (%d).\n",
264                                 strerror(errno), errno);
265                         break;
266                 }
267         }
268
269         ioctl(dev->fd, VIDIOC_STREAMON, &type);
270         return ret;
271 }
272
273 static int
274 uvc_video_set_format(struct uvc_device *dev)
275 {
276         struct v4l2_format fmt;
277         int ret;
278
279         printf("Setting format to 0x%08x %ux%u\n",
280                 dev->fcc, dev->width, dev->height);
281
282         memset(&fmt, 0, sizeof fmt);
283         fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
284         fmt.fmt.pix.width = dev->width;
285         fmt.fmt.pix.height = dev->height;
286         fmt.fmt.pix.pixelformat = dev->fcc;
287         fmt.fmt.pix.field = V4L2_FIELD_NONE;
288         if (dev->fcc == V4L2_PIX_FMT_MJPEG)
289                 fmt.fmt.pix.sizeimage = dev->imgsize * 1.5;
290
291         if ((ret = ioctl(dev->fd, VIDIOC_S_FMT, &fmt)) < 0)
292                 printf("Unable to set format: %s (%d).\n",
293                         strerror(errno), errno);
294
295         return ret;
296 }
297
298 static int
299 uvc_video_init(struct uvc_device *dev __attribute__((__unused__)))
300 {
301         return 0;
302 }
303
304 /* ---------------------------------------------------------------------------
305  * Request processing
306  */
307
308 struct uvc_frame_info
309 {
310         unsigned int width;
311         unsigned int height;
312         unsigned int intervals[8];
313 };
314
315 struct uvc_format_info
316 {
317         unsigned int fcc;
318         const struct uvc_frame_info *frames;
319 };
320
321 static const struct uvc_frame_info uvc_frames_yuyv[] = {
322         {  640, 360, { 666666, 10000000, 50000000, 0 }, },
323         { 1280, 720, { 50000000, 0 }, },
324         { 0, 0, { 0, }, },
325 };
326
327 static const struct uvc_frame_info uvc_frames_mjpeg[] = {
328         {  640, 360, { 666666, 10000000, 50000000, 0 }, },
329         { 1280, 720, { 50000000, 0 }, },
330         { 0, 0, { 0, }, },
331 };
332
333 static const struct uvc_format_info uvc_formats[] = {
334         { V4L2_PIX_FMT_YUYV, uvc_frames_yuyv },
335         { V4L2_PIX_FMT_MJPEG, uvc_frames_mjpeg },
336 };
337
338 static void
339 uvc_fill_streaming_control(struct uvc_device *dev,
340                            struct uvc_streaming_control *ctrl,
341                            int iframe, int iformat)
342 {
343         const struct uvc_format_info *format;
344         const struct uvc_frame_info *frame;
345         unsigned int nframes;
346
347         if (iformat < 0)
348                 iformat = ARRAY_SIZE(uvc_formats) + iformat;
349         if (iformat < 0 || iformat >= (int)ARRAY_SIZE(uvc_formats))
350                 return;
351         format = &uvc_formats[iformat];
352
353         nframes = 0;
354         while (format->frames[nframes].width != 0)
355                 ++nframes;
356
357         if (iframe < 0)
358                 iframe = nframes + iframe;
359         if (iframe < 0 || iframe >= (int)nframes)
360                 return;
361         frame = &format->frames[iframe];
362
363         memset(ctrl, 0, sizeof *ctrl);
364
365         ctrl->bmHint = 1;
366         ctrl->bFormatIndex = iformat + 1;
367         ctrl->bFrameIndex = iframe + 1;
368         ctrl->dwFrameInterval = frame->intervals[0];
369         switch (format->fcc) {
370         case V4L2_PIX_FMT_YUYV:
371                 ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2;
372                 break;
373         case V4L2_PIX_FMT_MJPEG:
374                 ctrl->dwMaxVideoFrameSize = dev->imgsize;
375                 break;
376         }
377         ctrl->dwMaxPayloadTransferSize = 512;   /* TODO this should be filled by the driver. */
378         ctrl->bmFramingInfo = 3;
379         ctrl->bPreferedVersion = 1;
380         ctrl->bMaxVersion = 1;
381 }
382
383 static void
384 uvc_events_process_standard(struct uvc_device *dev, struct usb_ctrlrequest *ctrl,
385                             struct uvc_request_data *resp)
386 {
387         printf("standard request\n");
388         (void)dev;
389         (void)ctrl;
390         (void)resp;
391 }
392
393 static void
394 uvc_events_process_control(struct uvc_device *dev, uint8_t req, uint8_t cs,
395                            struct uvc_request_data *resp)
396 {
397         printf("control request (req %02x cs %02x)\n", req, cs);
398         (void)dev;
399         (void)resp;
400 }
401
402 static void
403 uvc_events_process_streaming(struct uvc_device *dev, uint8_t req, uint8_t cs,
404                              struct uvc_request_data *resp)
405 {
406         struct uvc_streaming_control *ctrl;
407
408         printf("streaming request (req %02x cs %02x)\n", req, cs);
409
410         if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL)
411                 return;
412
413         ctrl = (struct uvc_streaming_control *)&resp->data;
414         resp->length = sizeof *ctrl;
415
416         switch (req) {
417         case UVC_SET_CUR:
418                 dev->control = cs;
419                 resp->length = 34;
420                 break;
421
422         case UVC_GET_CUR:
423                 if (cs == UVC_VS_PROBE_CONTROL)
424                         memcpy(ctrl, &dev->probe, sizeof *ctrl);
425                 else
426                         memcpy(ctrl, &dev->commit, sizeof *ctrl);
427                 break;
428
429         case UVC_GET_MIN:
430         case UVC_GET_MAX:
431         case UVC_GET_DEF:
432                 uvc_fill_streaming_control(dev, ctrl, req == UVC_GET_MAX ? -1 : 0,
433                                            req == UVC_GET_MAX ? -1 : 0);
434                 break;
435
436         case UVC_GET_RES:
437                 memset(ctrl, 0, sizeof *ctrl);
438                 break;
439
440         case UVC_GET_LEN:
441                 resp->data[0] = 0x00;
442                 resp->data[1] = 0x22;
443                 resp->length = 2;
444                 break;
445
446         case UVC_GET_INFO:
447                 resp->data[0] = 0x03;
448                 resp->length = 1;
449                 break;
450         }
451 }
452
453 static void
454 uvc_events_process_class(struct uvc_device *dev, struct usb_ctrlrequest *ctrl,
455                          struct uvc_request_data *resp)
456 {
457         if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
458                 return;
459
460         switch (ctrl->wIndex & 0xff) {
461         case UVC_INTF_CONTROL:
462                 uvc_events_process_control(dev, ctrl->bRequest, ctrl->wValue >> 8, resp);
463                 break;
464
465         case UVC_INTF_STREAMING:
466                 uvc_events_process_streaming(dev, ctrl->bRequest, ctrl->wValue >> 8, resp);
467                 break;
468
469         default:
470                 break;
471         }
472 }
473
474 static void
475 uvc_events_process_setup(struct uvc_device *dev, struct usb_ctrlrequest *ctrl,
476                          struct uvc_request_data *resp)
477 {
478         dev->control = 0;
479
480         printf("bRequestType %02x bRequest %02x wValue %04x wIndex %04x "
481                 "wLength %04x\n", ctrl->bRequestType, ctrl->bRequest,
482                 ctrl->wValue, ctrl->wIndex, ctrl->wLength);
483
484         switch (ctrl->bRequestType & USB_TYPE_MASK) {
485         case USB_TYPE_STANDARD:
486                 uvc_events_process_standard(dev, ctrl, resp);
487                 break;
488
489         case USB_TYPE_CLASS:
490                 uvc_events_process_class(dev, ctrl, resp);
491                 break;
492
493         default:
494                 break;
495         }
496 }
497
498 static void
499 uvc_events_process_data(struct uvc_device *dev, struct uvc_request_data *data)
500 {
501         struct uvc_streaming_control *target;
502         struct uvc_streaming_control *ctrl;
503         const struct uvc_format_info *format;
504         const struct uvc_frame_info *frame;
505         const unsigned int *interval;
506         unsigned int iformat, iframe;
507         unsigned int nframes;
508
509         switch (dev->control) {
510         case UVC_VS_PROBE_CONTROL:
511                 printf("setting probe control, length = %d\n", data->length);
512                 target = &dev->probe;
513                 break;
514
515         case UVC_VS_COMMIT_CONTROL:
516                 printf("setting commit control, length = %d\n", data->length);
517                 target = &dev->commit;
518                 break;
519
520         default:
521                 printf("setting unknown control, length = %d\n", data->length);
522                 return;
523         }
524
525         ctrl = (struct uvc_streaming_control *)&data->data;
526         iformat = clamp((unsigned int)ctrl->bFormatIndex, 1U,
527                         (unsigned int)ARRAY_SIZE(uvc_formats));
528         format = &uvc_formats[iformat-1];
529
530         nframes = 0;
531         while (format->frames[nframes].width != 0)
532                 ++nframes;
533
534         iframe = clamp((unsigned int)ctrl->bFrameIndex, 1U, nframes);
535         frame = &format->frames[iframe-1];
536         interval = frame->intervals;
537
538         while (interval[0] < ctrl->dwFrameInterval && interval[1])
539                 ++interval;
540
541         target->bFormatIndex = iformat;
542         target->bFrameIndex = iframe;
543         switch (format->fcc) {
544         case V4L2_PIX_FMT_YUYV:
545                 target->dwMaxVideoFrameSize = frame->width * frame->height * 2;
546                 break;
547         case V4L2_PIX_FMT_MJPEG:
548                 if (dev->imgsize == 0)
549                         printf("WARNING: MJPEG requested and no image loaded.\n");
550                 target->dwMaxVideoFrameSize = dev->imgsize;
551                 break;
552         }
553         target->dwFrameInterval = *interval;
554
555         if (dev->control == UVC_VS_COMMIT_CONTROL) {
556                 dev->fcc = format->fcc;
557                 dev->width = frame->width;
558                 dev->height = frame->height;
559
560                 uvc_video_set_format(dev);
561                 if (dev->bulk)
562                         uvc_video_stream(dev, 1);
563         }
564 }
565
566 static void
567 uvc_events_process(struct uvc_device *dev)
568 {
569         struct v4l2_event v4l2_event;
570         struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
571         struct uvc_request_data resp;
572         int ret;
573
574         ret = ioctl(dev->fd, VIDIOC_DQEVENT, &v4l2_event);
575         if (ret < 0) {
576                 printf("VIDIOC_DQEVENT failed: %s (%d)\n", strerror(errno),
577                         errno);
578                 return;
579         }
580
581         memset(&resp, 0, sizeof resp);
582         resp.length = -EL2HLT;
583
584         switch (v4l2_event.type) {
585         case UVC_EVENT_CONNECT:
586         case UVC_EVENT_DISCONNECT:
587                 return;
588
589         case UVC_EVENT_SETUP:
590                 uvc_events_process_setup(dev, &uvc_event->req, &resp);
591                 break;
592
593         case UVC_EVENT_DATA:
594                 uvc_events_process_data(dev, &uvc_event->data);
595                 return;
596
597         case UVC_EVENT_STREAMON:
598                 uvc_video_reqbufs(dev, 4);
599                 uvc_video_stream(dev, 1);
600                 return;
601
602         case UVC_EVENT_STREAMOFF:
603                 uvc_video_stream(dev, 0);
604                 uvc_video_reqbufs(dev, 0);
605                 return;
606         }
607
608         ioctl(dev->fd, UVCIOC_SEND_RESPONSE, &resp);
609         if (ret < 0) {
610                 printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno),
611                         errno);
612                 return;
613         }
614 }
615
616 static void
617 uvc_events_init(struct uvc_device *dev)
618 {
619         struct v4l2_event_subscription sub;
620
621         uvc_fill_streaming_control(dev, &dev->probe, 0, 0);
622         uvc_fill_streaming_control(dev, &dev->commit, 0, 0);
623
624         if (dev->bulk) {
625                 /* FIXME Crude hack, must be negotiated with the driver. */
626                 dev->probe.dwMaxPayloadTransferSize = 16 * 1024;
627                 dev->commit.dwMaxPayloadTransferSize = 16 * 1024;
628         }
629
630
631         memset(&sub, 0, sizeof sub);
632         sub.type = UVC_EVENT_SETUP;
633         ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
634         sub.type = UVC_EVENT_DATA;
635         ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
636         sub.type = UVC_EVENT_STREAMON;
637         ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
638         sub.type = UVC_EVENT_STREAMOFF;
639         ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
640 }
641
642 /* ---------------------------------------------------------------------------
643  * main
644  */
645
646 static void image_load(struct uvc_device *dev, const char *img)
647 {
648         int fd = -1;
649
650         if (img == NULL)
651                 return;
652
653         fd = open(img, O_RDONLY);
654         if (fd == -1) {
655                 printf("Unable to open MJPEG image '%s'\n", img);
656                 return;
657         }
658
659         dev->imgsize = lseek(fd, 0, SEEK_END);
660         lseek(fd, 0, SEEK_SET);
661         dev->imgdata = malloc(dev->imgsize);
662         if (dev->imgdata == NULL) {
663                 printf("Unable to allocate memory for MJPEG image\n");
664                 dev->imgsize = 0;
665                 return;
666         }
667
668         read(fd, dev->imgdata, dev->imgsize);
669         close(fd);
670 }
671
672 static void usage(const char *argv0)
673 {
674         fprintf(stderr, "Usage: %s [options]\n", argv0);
675         fprintf(stderr, "Available options are\n");
676         fprintf(stderr, " -b            Use bulk mode\n");
677         fprintf(stderr, " -d device     Video device\n");
678         fprintf(stderr, " -h            Print this help screen and exit\n");
679         fprintf(stderr, " -i image      MJPEG image\n");
680 }
681
682 int main(int argc, char *argv[])
683 {
684         char *device = "/dev/video0";
685         struct uvc_device *dev;
686         int bulk_mode = 0;
687         char *mjpeg_image = NULL;
688         fd_set fds;
689         int ret, opt;
690
691         while ((opt = getopt(argc, argv, "bd:hi:")) != -1) {
692                 switch (opt) {
693                 case 'b':
694                         bulk_mode = 1;
695                         break;
696
697                 case 'd':
698                         device = optarg;
699                         break;
700
701                 case 'h':
702                         usage(argv[0]);
703                         return 0;
704
705                 case 'i':
706                         mjpeg_image = optarg;
707                         break;
708
709                 default:
710                         fprintf(stderr, "Invalid option '-%c'\n", opt);
711                         usage(argv[0]);
712                         return 1;
713                 }
714         }
715
716         dev = uvc_open(device);
717         if (dev == NULL)
718                 return 1;
719
720         image_load(dev, mjpeg_image);
721
722         dev->bulk = bulk_mode;
723
724         uvc_events_init(dev);
725         uvc_video_init(dev);
726
727         FD_ZERO(&fds);
728         FD_SET(dev->fd, &fds);
729
730         while (1) {
731                 fd_set efds = fds;
732                 fd_set wfds = fds;
733
734                 ret = select(dev->fd + 1, NULL, &wfds, &efds, NULL);
735                 if (FD_ISSET(dev->fd, &efds))
736                         uvc_events_process(dev);
737                 if (FD_ISSET(dev->fd, &wfds))
738                         uvc_video_process(dev);
739         }
740
741         uvc_close(dev);
742         return 0;
743 }
744