Introducing PlantUML while reading the Linux driver

In this article, I will briefly introduce PlantUML while reading the Linux driver source.

What is PlantUML

Official site: https://plantuml.com/

Since there are many explanation articles on PlantUML, I will omit the details, but as the name suggests, it is a tool for writing UML. The feature is that it can be described in text.

UML is often used for design, but of course it can also be used for motion analysis. For example, when reading the driver source, isn't it possible that the function call relationship between files becomes complicated and the understanding cannot keep up? (Since my brain has only the capacity of a sparrow's tears, the tag jump is two steps. After a long time, I immediately forget what the caller was). It would be nice to be able to clarify who is calling the functions in what order, and sequence diagrams are useful in such cases.

When I write a sequence diagram with PlantUML, it looks like this.

@startuml
Alice -> Bob: SYN
Alice <-- Bob: ACK
@enduml

After creating the above with a text editor, save it with an appropriate name (example: hoge.puml) and generate an image with the following command.

$ plantuml hoge.puml

When executed, hoge.png will be generated in the directory where the command was executed.

hoge.png

The syntax is Object name arrow (such as'->' or'->') Object name: Message So, if you put the source file name in the object name and the function to call in the message, it seems that you can easily express the function call relationship between files. Please refer to the official website for more detailed syntax.

https://plantuml.com/ja/sequence-diagram

Read Linux driver source

As an example, let's take a quick look at the driver for a Linux v5.4 USB camera. It's under drivers / media / usb / uvc.

$ ls drivers/media/uvc/
Kconfig  Makefile  uvc_ctrl.c  uvc_debugfs.c  uvc_driver.c
uvc_entity.c  uvc_isight.c  uvc_metadata.c  uvc_queue.c
uvc_status.c  uvc_v4l2.c  uvc_video.c  uvcvideo.h

Why are there so many files?

The lowest level drivers for Linux USB (host controller and HUB control) are under drivers / usb / core. On the other hand, the UVC driver in drivers / media / uvc / is the part that works when the class of the connected USB device is UVC. I'm not sure what the files here are related to, so I'll continue reading with the goal of understanding the relationships between these files to some extent. However, since the purpose of this time is to introduce PlantUML **, I will not read it in detail.

First of all, it is a start. Since the UVC driver is a camera driver, it should be a mechanism that uses the Linux video capture framework (V4L2) to operate the camera according to ioctl () for / dev / videoXXX from the userland. Start reading with this assumption.

First of all, when you grep, it seems that uvc_driver.c, uvc_metadata.c, uvc_v4l2.c are processing ioctl related. As an example, let's follow the process of ioctl VIDIOC_S_FMT that sets the data format.

If you grep that kind of name, you'll find it in uvc_metadata.c and uvc_ioctl.c.

$ grep -iH -n vidioc_s_fmt drivers/media/usb/uvc/*
drivers/media/usb/uvc/uvc_metadata.c:133:	.vidioc_s_fmt_meta_cap		= uvc_meta_v4l2_set_format,
drivers/media/usb/uvc/uvc_v4l2.c:472: * - VIDIOC_S_FMT
drivers/media/usb/uvc/uvc_v4l2.c:1474:	.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
drivers/media/usb/uvc/uvc_v4l2.c:1475:	.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,

Both files set their functions in the function pointers of the v4l2_ioctl_ops structure.

ioctl VIDIOC_S_FMT is an API that takes a pointer to a v4l2_format structure as an argument, and when you call ioctl (), the first entry is v4l_s_fmt () in drivers / media / v4l2-core / v4l2-ioctl.c. As you can see here, this API is called by specifying the type to be set in the type of the v4l2_format structure. v4l2_s_fmt () calls the function pointer set in the v4l2_ioctl_ops structure according to the type.

v4l2-ioctl.c


static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
...
	switch (p->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
		if (unlikely(!ops->vidioc_s_fmt_vid_cap))
			break;
		CLEAR_AFTER_FIELD(p, fmt.pix);
		ret = ops->vidioc_s_fmt_vid_cap(file, fh, arg);
		/* just in case the driver zeroed it again */
		p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC;
		if (vfd->vfl_type == VFL_TYPE_TOUCH)
			v4l_pix_format_touch(&p->fmt.pix);
		return ret;
...

When V4L2_BUF_TYPE_VIDEO_CAPTURE is specified for type, it seems to enter the call path of the function uvc_ioctl_s_fmt_vid_cap () set in .vidioc_s_fmt_vid_cap in uvc_v4l2.c.

So, if you express this in PlantUML

@startuml
participant "User Space" as user
participant "v4l2-ioctl.c" as v4l2
participant "uvc_v4l2.c" as uvc

user -> v4l2: ioctl(fd, V4L2_S_FMT, *v4l2_format)
alt v4l2_format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE
	v4l2 -> uvc: uvc_ioctl_s_fmt_vid_cap()
end
@enduml

sample.png

How about something like this? The reason why "participant ~ as ..." is added at the beginning is that if a hyphen'-'is included in the object name, it is necessary to enclose it in double quotes for grammatical reasons, so it is troublesome to write each one, so an alias is given.

We will follow the function calls in the future a little deeper.

uvc_v4l2.c


static int uvc_ioctl_s_fmt_vid_cap(struct file *file, void *fh,
				   struct v4l2_format *fmt)
{
	struct uvc_fh *handle = fh;
	struct uvc_streaming *stream = handle->stream;
	int ret;

	ret = uvc_acquire_privileges(handle);
	if (ret < 0)
		return ret;

	return uvc_v4l2_set_format(stream, fmt);
}

Both uvc_acquire_privileges () and uvc_v4l2_set_format () called from here are functions of uvc_v4l2.c, but uvc_v4l2_set_format () calls uvc_v4l2_try_format () after that and drives /media/usb/uvc/uvc_video.vc ). Then call ubc_set_video_ctrl (), __uvc_query_ctrl (), and finally call the USB core function usb_control_msg ().

If you want to write in more detail, you can write all the function calls in the previous sequence. Let's write all the function calls in order.

@startuml
participant "User Space" as user
participant "v4l2-ioctl.c" as v4l2
participant "uvc_v4l2.c" as uvc
participant "uvc_video.c" as uvc_video
participant "usb/core/message.c " as usb_core

user -> v4l2: ioctl(fd, V4L2_S_FMT, *v4l2_format)
alt v4l2_format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE
	v4l2 -> uvc: uvc_ioctl_s_fmt_vid_cap()
	uvc -> uvc: uvc_v4l2_try_format()
	uvc -> uvc_video:  uvc_probe_video()
	uvc_video -> uvc_video: uvc_set_video_ctrl()
	uvc_video -> uvc_video: __uvc_query_ctrl()
	uvc_video -> usb_core: usb_control_msg()
end
@enduml

sample2.png

If you read this far, you can see the rough structure from the call of ioctl () from the userland to the command being thrown to the USB device. Also, it can be expected that uvc_v4l2.c is responsible for v4l2 ioctl () processing and uvc_video.c is responsible for message processing related to USB communication.

Summary

In this article, I introduced PlantUML using the Linux source code written in C as an example.

Personally, I like it because I can write it intuitively like writing a memo on the call stack. Is it a bottleneck that requires Java or needs to be built every time (this can be solved with Atom or VSCode plugins so check out other articles)? However, being able to write in text is a huge advantage.

Examples of using PlantUML in design have been introduced on several sites, but I did not find many examples of using it for analysis in this way, so I wrote it. Please use it when you want to sit down and read the source.

If you use the Atom or VS Code plug-in, you can automatically generate a preview without hitting the build command each time. There are many such introductory articles, so please search for them.

Recommended Posts

Introducing PlantUML while reading the Linux driver
The Linux Watchdog driver API
Control the Linux trackpad