博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
v4l2 vivi驱动分析
阅读量:3557 次
发布时间:2019-05-20

本文共 38306 字,大约阅读时间需要 127 分钟。

v4l2驱动框架相对还是挺复杂的,

 

最好的参考例子有

v4l2-pci-skeleton.c

vivi (Virtual Video)


其中vivi在最新的Linux 4.xx版本也变得非常复杂。

所以采用Linux-3.16.74版本作为学习...


1. 该版本没有使用platform_driver框架, 故模块初始化入口函数vivi_init直接进行video设备的注册。

(如果使用platform driver的框架, 会在probe函数中进行注册)

 

2. vivi_init初始化过程, 最主要的动作就是创建vivi实例。

主要函数为vivi_create_instance

通常的, 里面会分配好vivi设备描述结构体, vivi_dev

创建v4l2_dev设备, 调用v4l2_device_register注册一个v4l2设备,  这个相比video_device来说, 相当于一个父节点, 描述的信息更加common, 注册函数中会对该设备进行一些通用的初始化和资源分配。

 

下一步是构建video_device并注册该设备。

关于ctrl_handler, 如亮度, 对比度, 透明度等等暂时先不具体研究。

 

4. 创建vb2_queue, video设备内部使用的队列。

该结构体也很复杂, 直接贴图...

struct vb2_queue {                                                                                                                                 ›   enum v4l2_buf_type› ›   type;                                                                                                                  ›   unsigned int›   ›   ›   io_modes;                                                                                                              ›   unsigned int›   ›   ›   io_flags;                                                                                                              ›   struct mutex›   ›   ›   *lock;                                                                                                                 ›   struct v4l2_fh› ›   ›   *owner;                                                                                                                                                                                                                                                                   ›   const struct vb2_ops›   ›   *ops;                                                                                                              ›   const struct vb2_mem_ops›   *mem_ops;                                                                                                          ›   void›   ›   ›   ›   *drv_priv;                                                                                                                 ›   unsigned int›   ›   ›   buf_struct_size;                                                                                                       ›   u32››   ›   ›   timestamp_flags;                                                                                                               ›   gfp_t›  ›   ›   ›   gfp_flags;                                                                                                                 ›   u32››   ›   ›   min_buffers_needed;                                                                                                                                                                                                                                                               /* private: internal use only */                                                                                                                   ›   enum v4l2_memory›   ›   memory;                                                                                                                ›   struct vb2_buffer›  ›   *bufs[VIDEO_MAX_FRAME];                                                                                                ›   unsigned int›   ›   ›   num_buffers;                                                                                                                                                                                                                                                              ›   struct list_head›   ›   queued_list;                                                                                                           ›   unsigned int›   ›   ›   queued_count;                                                                                                                                                                                                                                                             ›   atomic_t›   ›   ›   owned_by_drv_count;                                                                                                        ›   struct list_head›   ›   done_list;                                                                                                             ›   spinlock_t› ›   ›   done_lock;                                                                                                                 ›   wait_queue_head_t›  ›   done_wq;                                                                                                                                                                                                                                                                  ›   void›   ›   ›   ›   *alloc_ctx[VIDEO_MAX_PLANES];                                                                                              ›   unsigned int›   ›   ›   plane_sizes[VIDEO_MAX_PLANES];                                                                                                                                                                                                                                            ›   unsigned int›   ›   ›   streaming:1;                                                                                                           ›   unsigned int›   ›   ›   start_streaming_called:1;                                                                                              ›   unsigned int›   ›   ›   waiting_for_buffers:1;                                                                                                                                                                                                                                                   m›   struct vb2_fileio_data› ›   *fileio;                                                                                                           ›   struct vb2_threadio_data›   *threadio;

可以看到vb2_queue内部有两个队列, queued_list和done_list

即用户req bufs并mmap到用户态后, 调用queue buffer时,  会将这些空buffer放入queued_list,

vivi设备内部线程, 或工作队列,  或定时器等 隔段时间将要显示的yuv数据填充到申请的缓存中。

并放入done_list,  即完成队列中。

如此,  用户态就能通过dequeue buffer取出done_list中的数据, 进行渲染, 存储或传输等操作了。

 

5. 继续填充video_device数据结构, 比如file_operations, ioctls等等

1457 ›   vfd = &dev->vdev;                                                                                                                        1458 ›   *vfd = vivi_template;                                                                                                                    1459 ›   vfd->debug = debug;                                                                                                                      1460 ›   vfd->v4l2_dev = &dev->v4l2_dev;                                                                                                          1461 ›   vfd->queue = q;

 

6. 设备都填充好后, 调用video_register_device即可


 

ok, 是不是很简单...

是的,  当你对linux设备框架理解后, 它就会变得越来越简单...

所以我还需不断实践加总结, 加深理解...


后面我们跟下代码, 最关键的VIDIOC_QBUF和VIDIOC_DQBUF,

看下内部的buffer是如何流动的。

 

1. VIDIOC_QBUF - >

查看vivi_ioctl_ops

1320 static const struct v4l2_ioctl_ops vivi_ioctl_ops = {                                                                                        1321 ›   .vidioc_querycap      = vidioc_querycap,                                                                                                 1322 ›   .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,                                                                                     1323 ›   .vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,                                                                                        1324 ›   .vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,                                                                                      1325 ›   .vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,                                                                                        1326 ›   .vidioc_enum_framesizes   = vidioc_enum_framesizes,                                                                                      1327 ›   .vidioc_reqbufs       = vb2_ioctl_reqbufs,                                                                                                1328 ›   .vidioc_create_bufs   = vb2_ioctl_create_bufs,                                                                                            1329 ›   .vidioc_prepare_buf   = vb2_ioctl_prepare_buf,                                                                                            1330 ›   .vidioc_querybuf      = vb2_ioctl_querybuf,                                                                                               1331 ›   .vidioc_qbuf          = vb2_ioctl_qbuf,                                                                                                   1332 ›   .vidioc_dqbuf         = vb2_ioctl_dqbuf,                                                                                                  1333 ›   .vidioc_enum_input    = vidioc_enum_input,                                                                                                1334 ›   .vidioc_g_input       = vidioc_g_input,                                                                                                   1335 ›   .vidioc_s_input       = vidioc_s_input,                                                                                                   1336 ›   .vidioc_enum_frameintervals = vidioc_enum_frameintervals,                                                                                 1337 ›   .vidioc_g_parm        = vidioc_g_parm,                                                                                                    1338 ›   .vidioc_s_parm        = vidioc_s_parm,                                                                                                    1339 ›   .vidioc_streamon      = vb2_ioctl_streamon,                                                                                               1340 ›   .vidioc_streamoff     = vb2_ioctl_streamoff,                                                                                              1341 ›   .vidioc_log_status    = v4l2_ctrl_log_status,                                                                                             1342 ›   .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,                                                                                     m1343 ›   .vidioc_unsubscribe_event = v4l2_event_unsubscribe,                                                                                       1344 };                                                                                                                                           1345                                                                                                                                              1346 static const struct video_device vivi_template = {                                                                                           1347 ›   .name›  ›   = "vivi",                                                                                                                    1348 ›   .fops           = &vivi_fops,                                                                                                            1349 ›   .ioctl_ops ›= &vivi_ioctl_ops,                                                                                                           1350 ›   .release›   = video_device_release_empty,                                                                                                1351 };

对应实现为vb2_ioctl_qbuf, 

是的v4l2-core核心层帮忙实现的逻辑越来越多...这个queue buffer的操作也有通用的一些流程了...

 

2. vb2_ioctl_qbuf

-> vb2_qbuf->vb2_internal_qbuf->

1785 static int vb2_internal_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)                                                                     1786 {                                                                                                                                            1787 ›   int ret = vb2_queue_or_prepare_buf(q, b, "qbuf");                                                                                        1788 ›   struct vb2_buffer *vb;                                                                                                                   1789                                                                                                                                              1790 ›   if (ret)                                                                                                                                 1791 ›   ›   return ret;                                                                                                                          1792                                                                                                                                              1793 ›   vb = q->bufs[b->index];                                                                                                                  1794                                                                                                                                              1795 ›   switch (vb->state) {                                                                                                                     1796 ›   case VB2_BUF_STATE_DEQUEUED:                                                                                                             1797 ›   ›   ret = __buf_prepare(vb, b);                                                                                                          1798 ›   ›   if (ret)                                                                                                                             1799 ›   ›   ›   return ret;                                                                                                                      1800 ›   ›   break;                                                                                                                               1801 ›   case VB2_BUF_STATE_PREPARED:                                                                                                             1802 ›   ›   break;                                                                                                                               1803 ›   case VB2_BUF_STATE_PREPARING:                                                                                                            1804 ›   ›   dprintk(1, "buffer still being prepared\n");                                                                                         1805 ›   ›   return -EINVAL;                                                                                                                      1806 ›   default:                                                                                                                                 1807 ›   ›   dprintk(1, "invalid buffer state %d\n", vb->state);                                                                                  1808 ›   ›   return -EINVAL;                                                                                                                      1809 ›   }                                                                                                                                        1810                                                                                                                                              1811 ›   /*                                                                                                                                       1812 ›    * Add to the queued buffers list, a buffer will stay on it until                                                                        1813 ›    * dequeued in dqbuf.                                                                                                                    1814 ›    */                                                                                                                                      1815 ›   list_add_tail(&vb->queued_entry, &q->queued_list);                                                                                       1816 ›   q->queued_count++;                                                                                                                       1817 ›   q->waiting_for_buffers = false;                                                                                                          1818 ›   vb->state = VB2_BUF_STATE_QUEUED;

首先vb2_queue_or_prepare_buf(q, b, "buf"), 进去看了下, 貌似没干啥事, 很多是检查数据合法性。先不管了。

下面vb = q->bufs[b->index];

即从vb2_buffer中取得当前要入列的帧。 是的, vb2_queue内部使用vb2_buffer来对buffer进行描述。

vb2_buffer中有两个list_head, 分别是queued_entry和done_entry, 

list_head结构很简单, 就两个指针, 分别指向前一个和下一个。(对linux kernel里的list就是一个双向链表)

struct list_head {                                                                                                                                                                                           

›   struct list_head *next, *prev;                                                                                                                                                                           
};

在reqbufs阶段,  会根据用户需求, 分配好需求的帧缓存, 其中vb2_buffer就是描述帧缓存的信息。

vb2_buffer分配好后,  queued_entry和done_entry同时被分配出来。入队出队的就是list_head这个指针。

看到注释那里, 就是将vb->queued_entry放入到vb2的queued_list中。 入列就完成了...

ok, queued_list中的buffer 最终是要被vivi设备拿来处理的...嗯 需要填充后丢给done_list..

那queued_list中的buffer如何取出并处理呢?

 

3. 关于queued_list中的数据如何取出并处理?

直接搜索queued_list 貌似没搜着... (可能又用了一些啥技巧...比如CALL(a, b, op) 啥的)....

但根据常识, 将queued_list中的buffer取出并进行处理的过程(这里是填充数据), 不应该由v4l2-core核心来实现,

而应该由具体的驱动来实现。 这里是vivi.c

所以去vivi.c中进行查看...

677 static void vivi_thread_tick(struct vivi_dev *dev)                                                                                                                                                                      678 {                                                                                                                                                                                                                       679 ›   struct vivi_dmaqueue *dma_q = &dev->vidq;                                                                                                                                                                           680 ›   struct vivi_buffer *buf;                                                                                                                                                                                            681 ›   unsigned long flags = 0;                                                                                                                                                                                            682                                                                                                                                                                                                                         683 ›   dprintk(dev, 1, "Thread tick\n");                                                                                                                                                                                   684                                                                                                                                                                                                                         685 ›   spin_lock_irqsave(&dev->slock, flags);                                                                                                                                                                              686 ›   if (list_empty(&dma_q->active)) {                                                                                                                                                                                   687 ›   ›   dprintk(dev, 1, "No active queue to serve\n");                                                                                                                                                                  688 ›   ›   spin_unlock_irqrestore(&dev->slock, flags);                                                                                                                                                                     689 ›   ›   return;                                                                                                                                                                                                         690 ›   }                                                                                                                                                                                                                   691                                                                                                                                                                                                                         692 ›   buf = list_entry(dma_q->active.next, struct vivi_buffer, list);                                                                                                                                                     693 ›   list_del(&buf->list);                                                                                                                                                                                               694 ›   spin_unlock_irqrestore(&dev->slock, flags);                                                                                                                                                                         695                                                                                                                                                                                                                         696 ›   v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);                                                                                                                                                                    697                                                                                                                                                                                                                         698 ›   /* Fill buffer */                                                                                                                                                                                                   699 ›   vivi_fillbuff(dev, buf);                                                                                                                                                                                            700 ›   dprintk(dev, 1, "filled buffer %p\n", buf);                                                                                                                                                                         701                                                                                                                                                                                                                         702 ›   vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);                                                                                                                                                                      703 ›   dprintk(dev, 2, "[%p/%d] done\n", buf, buf->vb.v4l2_buf.index);                                                                                                                                                     704 }

很容易找到, vivi.c中拉起了一个内核线程。

主要工作是定时从dma_q->active队列中取出buffer进行填充。

查看vivi_dmaqueue数据结构,

192 struct vivi_dmaqueue {                                                                                                                                                                                                  193 ›   struct list_head       active;                                                                                                                                                                                      194                                                                                                                                                                                                                         195 ›   /* thread for generating video stream*/                                                                                                                                                                             196 ›   struct task_struct         *kthread;                                                                                                                                                                                197 ›   wait_queue_head_t          wq;                                                                                                                                                                                      198 ›   /* Counters to control fps rate */                                                                                                                                                                                  199 ›   int                        frame;                                                                                                                                                                                   200 ›   int                        ini_jiffies;                                                                                                                                                                             201 };

内部有个active队列。 

即从活跃的队列中取出数据进行填充。 ok, 继续...

875 static void buffer_queue(struct vb2_buffer *vb)                                                                                                                                                                         876 {                                                                                                                                                                                                                       877 ›   struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);                                                                                                                                                             878 ›   struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb);                                                                                                                                                 879 ›   struct vivi_dmaqueue *vidq = &dev->vidq;                                                                                                                                                                            880 ›   unsigned long flags = 0;                                                                                                                                                                                            881                                                                                                                                                                                                                         882 ›   dprintk(dev, 1, "%s\n", __func__);                                                                                                                                                                                  883                                                                                                                                                                                                                         884 ›   spin_lock_irqsave(&dev->slock, flags);                                                                                                                                                                              885 ›   list_add_tail(&buf->list, &vidq->active);                                                                                                                                                                           886 ›   spin_unlock_irqrestore(&dev->slock, flags);                                                                                                                                                                         887 }

在buffer_queue函数中找到了, 将vivi_buffer的list_head放入活跃队列的尾部。

看下vivi_buffer的结构体

185 /* buffer for one video frame */                                                                                                                                                                                        186 struct vivi_buffer {                                                                                                                                                                                                    187 ›   /* common v4l buffer stuff -- must be first */                                                                                                                                                                      188 ›   struct vb2_buffer›  vb;                                                                                                                                                                                             189 ›   struct list_head›   list;                                                                                                                                                                                           190 };

关于buffer_queue的调用,

929 static const struct vb2_ops vivi_video_qops = {                                                                                                                                                                         930 ›   .queue_setup›   ›   = queue_setup,                                                                                                                                                                                  931 ›   .buf_prepare›   ›   = buffer_prepare,                                                                                                                                                                               932 ›   .buf_queue› ›   = buffer_queue,                                                                                                                                                                                     933 ›   .start_streaming›   = start_streaming,                                                                                                                                                                              934 ›   .stop_streaming››   = stop_streaming,                                                                                                                                                                               935 ›   .wait_prepare›  ›   = vivi_unlock,                                                                                                                                                                                  936 ›   .wait_finish›   ›   = vivi_lock,                                                                                                                                                                                    937 };

它是vb2_queue队列的一个操作回调函数。

它何时会被调用?

搜索下只能在videobuf-core.c中找到...

但我们是vb2... 所以到

videobuf2-core.c中寻找, 果然使用了技巧...

/** * __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing */static void __enqueue_in_driver(struct vb2_buffer *vb){	struct vb2_queue *q = vb->vb2_queue;	unsigned int plane;	vb->state = VB2_BUF_STATE_ACTIVE;	atomic_inc(&q->owned_by_drv_count);	/* sync buffers */	for (plane = 0; plane < vb->num_planes; ++plane)		call_void_memop(vb, prepare, vb->planes[plane].mem_priv);	call_void_vb_qop(vb, buf_queue, vb);}

看注释... __enqueue_in_driver这个函数将最终会利用call_void_vb_qop 调用buf_queue函数, 进行驱动层面的数据处理。

如果是编解码器, 需要丢给编解码器进行数据处理。

如果是摄像头, 需要把摄像头采集的数据填充给这一个buffer。

我们这里是vivi驱动, buf_queue函数会将vb2_buffer放入活跃队列中。

内核线程会从活跃队列中取数据并填充。最终放入done_list中。


ok, 我们看看__enqueue_in_driver被谁调用了?

很好找,  就在之前描述的v4l2_internel_qbuf中... 之前没贴全... 我们继续贴代码。

1811 ›   /*                                                                                                                                       1812 ›    * Add to the queued buffers list, a buffer will stay on it until                                                                        1813 ›    * dequeued in dqbuf.                                                                                                                    1814 ›    */                                                                                                                                      1815 ›   list_add_tail(&vb->queued_entry, &q->queued_list);                                                                                       1816 ›   q->queued_count++;                                                                                                                       1817 ›   q->waiting_for_buffers = false;                                                                                                          1818 ›   vb->state = VB2_BUF_STATE_QUEUED;                                                                                                        1819 ›   if (V4L2_TYPE_IS_OUTPUT(q->type)) {                                                                                                      1820 ›   ›   /*                                                                                                                                   1821 ›   ›    * For output buffers copy the timestamp if needed,                                                                                  1822 ›   ›    * and the timecode field and flag if needed.                                                                                        1823 ›   ›    */                                                                                                                                  1824 ›   ›   if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==                                                                           1825 ›   ›       V4L2_BUF_FLAG_TIMESTAMP_COPY)                                                                                                    1826 ›   ›   ›   vb->v4l2_buf.timestamp = b->timestamp;                                                                                           1827 ›   ›   vb->v4l2_buf.flags |= b->flags & V4L2_BUF_FLAG_TIMECODE;                                                                             1828 ›   ›   if (b->flags & V4L2_BUF_FLAG_TIMECODE)                                                                                               1829 ›   ›   ›   vb->v4l2_buf.timecode = b->timecode;                                                                                             1830 ›   }                                                                                                                                        1831                                                                                                                                              1832 ›   /*                                                                                                                                       1833 ›    * If already streaming, give the buffer to driver for processing.                                                                       1834 ›    * If not, the buffer will be given to driver on next streamon.                                                                          1835 ›    */                                                                                                                                      1836 ›   if (q->start_streaming_called)                                                                                                           1837 ›   ›   __enqueue_in_driver(vb);

所以这个数据放入queued_list的同时, 又调用驱动的buf_queue, 放入了vivi驱动中的active队列。

但这个buffer并没有从queued_list中删除, 那具体数据会在什么时候被删除呢?

根据注释的意思, a buffer will stay on it util dequeued in dqbuf。

是的这个buffer会在dqbuf时才会被删除...

其实也好理解, 对于用户态来说, 它并不认为是对两个队列进行操作, dequeue和enqueue的感觉上是同一个队列。。。

从队列头部取一个, 处理完, 放入队列尾部, 感觉上就是这样的。

 

所以vivi设备内部的buf虽然已经从queued_list取出并放入活跃队列, 并最终进行数据填充放入done_list。

但该数据不会从queued_list中删除, 只有当dequeue buffer成功后, 才会在queued_list中删除!!! 

 

后面继续看dequeue buffer的逻辑.


 

4. VIDIOC_DQBUF

很快找到, vivi驱动中会调用vb2_ioctl_dqbuf

 

5. vb2_ioctl_dqbuf

-> vb2_dqbuf -> vb2_internal_dqbuf -> 

static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking){	struct vb2_buffer *vb = NULL;	int ret;	if (b->type != q->type) {		dprintk(1, "invalid buffer type\n");		return -EINVAL;	}	ret = __vb2_get_done_vb(q, &vb, b, nonblocking);	if (ret < 0)		return ret;	switch (vb->state) {	case VB2_BUF_STATE_DONE:		dprintk(3, "returning done buffer\n");		break;	case VB2_BUF_STATE_ERROR:		dprintk(3, "returning done buffer with errors\n");		break;	default:		dprintk(1, "invalid buffer state\n");		return -EINVAL;	}	call_void_vb_qop(vb, buf_finish, vb);	/* Fill buffer information for the userspace */	__fill_v4l2_buffer(vb, b);	/* Remove from videobuf queue */	list_del(&vb->queued_entry);	q->queued_count--;	/* go back to dequeued state */	__vb2_dqbuf(vb);	dprintk(1, "dqbuf of buffer %d, with state %d\n",			vb->v4l2_buf.index, vb->state);	/*	 * After calling the VIDIOC_DQBUF V4L2_BUF_FLAG_DONE must be	 * cleared.	 */	b->flags &= ~V4L2_BUF_FLAG_DONE;	return 0;}

代码不长,  直接全贴了。逻辑很简单, 从done_list取出一个buffer,  并从done_list中删除,  填充到v4l2_buffer中给用户态,

同时删除queued_entry!!!

 

关键函数是 __vb2_get_done_vb

/** * __vb2_get_done_vb() - get a buffer ready for dequeuing * * Will sleep if required for nonblocking == false. */static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,				struct v4l2_buffer *b, int nonblocking){	unsigned long flags;	int ret;	/*	 * Wait for at least one buffer to become available on the done_list.	 */	ret = __vb2_wait_for_done_vb(q, nonblocking);	if (ret)		return ret;	/*	 * Driver's lock has been held since we last verified that done_list	 * is not empty, so no need for another list_empty(done_list) check.	 */	spin_lock_irqsave(&q->done_lock, flags);	*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);	/*	 * Only remove the buffer from done_list if v4l2_buffer can handle all	 * the planes.	 */	ret = __verify_planes_array(*vb, b);	if (!ret)		list_del(&(*vb)->done_entry);	spin_unlock_irqrestore(&q->done_lock, flags);	return ret;}

可以看到,  从done_list中取出一个数据, 并在done_list中删除该数据。

转载地址:http://jpcrj.baihongyu.com/

你可能感兴趣的文章
[LeetCode javaScript] 860. 柠檬水找零
查看>>
[LeetCode javaScript] 118. 杨辉三角
查看>>
[LeetCode javaScript] 905. 按奇偶校验排序数组
查看>>
[LeetCode javaScript] 617. 合并二叉树
查看>>
[LeetCode javaScript] 292. Nim游戏
查看>>
[LeetCode javaScript] 896. 单调数列
查看>>
[LeetCode javaScript] 804. 唯一摩尔斯密码词
查看>>
[LeetCode javaScript] 476. 数字的补数
查看>>
[LeetCode javaScript] 811. 子域名访问计数
查看>>
[LeetCode javaScript] 414. 第三大的数
查看>>
[LeetCode javaScript] 242. 有效的字母异位词
查看>>
[LeetCode javaScript] 75. 颜色分类
查看>>
[LeetCode javaScript] 56. 合并区间
查看>>
[LeetCode javaScript] 190. 颠倒二进制位
查看>>
[LeetCode javaScript] 521. 最长特殊序列 Ⅰ
查看>>
[LeetCode javaScript] 806. 写字符串需要的行数
查看>>
[LeetCode javaScript] 868. 二进制间距
查看>>
[LeetCode javaScript] 824. 山羊拉丁文
查看>>
[LeetCode javaScript] 463. 岛屿的周长
查看>>
[LeetCode javaScript] 107. 二叉树的层次遍历 II
查看>>