{"id":5668,"date":"2026-09-11T18:00:43","date_gmt":"2026-09-11T09:00:43","guid":{"rendered":"https:\/\/donguri3.net\/server-tech\/linux-udev-device-symlink-csi-camera-2\/"},"modified":"2026-09-11T18:00:44","modified_gmt":"2026-09-11T09:00:44","slug":"linux-udev-device-symlink-csi-camera","status":"publish","type":"post","link":"https:\/\/donguri3.net\/en\/server-tech\/linux-server-network\/linux-udev-device-symlink-csi-camera\/","title":{"rendered":"Fixing Linux Device Name Fluctuations After Reboot Using udev (Practical Example with PiKVM CSI Camera)"},"content":{"rendered":"<p>In a PiKVM environment built on a Raspberry Pi 3 Model B, we introduced a CSI-connected camera module to externally monitor the server&#8217;s front LED indicators and physical operational status. For the video streaming daemon, we adopted the lightweight and low-latency ustreamer (service name: ustreamer-csi), configured to stream MJPEG via port 8081.<\/p>\n<p>During the initial setup, \/dev\/video0 was specified directly in the configuration file, and we confirmed that the video could be previewed smoothly via a web browser without any issues.<\/p>\n<h2>Implementing a Mechanism to Reliably Recognize the Camera Device After Reboots<\/h2>\n<p>However, after rebooting the system, checking uStreamer&#8217;s streaming status revealed that while the web server itself responded, the video stream had stopped.<\/p>\n<p>Upon checking the API endpoint (\/state), we found &#8220;online&#8221;: false and &#8220;captured_fps&#8221;: 0 as shown below, indicating that not a single frame was being acquired from the device.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n{\n&quot;ok&quot;: true,\n&quot;result&quot;: {\n&quot;source&quot;: {\n&quot;resolution&quot;: {\n&quot;width&quot;: 640,\n&quot;height&quot;: 480\n},\n&quot;online&quot;: false,\n&quot;desired_fps&quot;: 0,\n&quot;captured_fps&quot;: 0\n},\n&quot;stream&quot;: {\n&quot;queued_fps&quot;: 0,\n&quot;clients&quot;: 0\n}\n}\n}\n<\/pre>\n<p>Logging in via SSH after every reboot to check the device number and manually reconfigure the service is not practical. We needed to implement a mechanism that would automatically and reliably recognize the camera device and resume streaming even after an OS reboot, without human intervention.<\/p>\n<h2>Dynamic Allocation of \/dev\/video Numbers and Raspberry Pi-Specific Device Structures<\/h2>\n<p>To identify the cause of the issue, we checked the kernel logs.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">dmesg | grep video<\/pre>\n<p>Part of the output was as follows:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nbcm2835-isp bcm2835-isp: Device node output&#x5B;0] registered as \/dev\/video13\nbcm2835-codec bcm2835-codec: Device registered as \/dev\/video10\nbcm2835_v4l2-0: V4L2 device registered as video2 - stills mode &gt; 1280x720\nbcm2835-codec bcm2835-codec: Device registered as \/dev\/video18\n<\/pre>\n<p>Due to the kernel initialization order, the bcm2835-v4l2 device for the CSI camera was registered as \/dev\/video2. Because the service definition hardcoded \/dev\/video0, a mismatch occurred, causing the open operation to fail.<\/p>\n<p>The challenge here is the large number of V4L2 devices unique to the Raspberry Pi. Since the SoC&#8217;s internal hardware codec (bcm2835-codec) and Image Signal Processor (bcm2835-isp) generate numerous nodes (video10 to video23, etc.), simply specifying an &#8220;available video node&#8221; cannot accurately pinpoint the correct camera device.<\/p>\n<p>Also, it is important to note that conditions like ATTR{name}==&#8221;mmal service 16.1&#8243; found in older documentation do not match current environments, and on actual hardware, it is identified as camera0.<\/p>\n<h2>Generating a Fixed Symbolic Link (\/dev\/csi-camera) via udev Rules<\/h2>\n<p>Rather than trying to fix the device number itself, we use udev rules to automatically generate a unique symbolic link, \/dev\/csi-camera, that always points to the CSI camera.<\/p>\n<h3>Checking Actual Device Attributes<\/h3>\n<p>First, we extracted attributes available for udev conditions from the device currently recognized as the camera.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">udevadm info --attribute-walk --name=\/dev\/video2<\/pre>\n<p>The key attributes extracted were as follows:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nlooking at device &#039;\/devices\/platform\/soc\/3f00b840.mailbox\/bcm2835-camera\/video4linux\/video2&#039;:\nKERNEL==&quot;video2&quot;\nSUBSYSTEM==&quot;video4linux&quot;\nATTR{index}&quot;0&quot;\nATTR{name}&quot;camera0&quot;\n\nlooking at parent device &#039;\/devices\/platform\/soc\/3f00b840.mailbox\/bcm2835-camera&#039;:\nKERNELS==&quot;bcm2835-camera&quot;\nSUBSYSTEMS==&quot;vchiq-bus&quot;\nDRIVERS==&quot;bcm2835-camera&quot;\n<\/pre>\n<p>To avoid confusing it with other device nodes, we ensured uniqueness by combining ATTR{name}==&#8221;camera0&#8243; with the parent device&#8217;s KERNELS==&#8221;bcm2835-camera&#8221; and DRIVERS==&#8221;bcm2835-camera&#8221;.<\/p>\n<h3>Creating and Applying udev Rules<\/h3>\n<p>Make the PiKVM file system writable and create the rule file.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">rwnano \/etc\/udev\/rules.d\/99-csi-camera.rules<\/pre>\n<p>Write the following single line into the file \/etc\/udev\/rules.d\/99-csi-camera.rules.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nSUBSYSTEM==&quot;video4linux&quot;, ATTR{name}&quot;camera0&quot;, KERNELS&quot;bcm2835-camera&quot;, DRIVERS==&quot;bcm2835-camera&quot;, ATTR{index}==&quot;0&quot;, SYMLINK+=&quot;csi-camera&quot;, TAG+=&quot;systemd&quot;, ENV{SYSTEMD_ALIAS}=&quot;\/dev\/csi-camera&quot;\n<\/pre>\n<p>By adding TAG+=&#8221;systemd&#8221; and ENV{SYSTEMD_ALIAS}=&#8221;\/dev\/csi-camera&#8221;, the udev-generated link is recognized as a systemd device unit (dev-csi\\x2dcamera.device). After setting this up, reload the rules to apply them.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nudevadm control --reload-rules\nudevadm trigger --subsystem-match=video4linux\nudevadm settle\nro\n<\/pre>\n<h3>Verifying the Symbolic Link<\/h3>\n<p>Check if the link is generated normally.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nls -l \/dev\/csi-camera\nreadlink -f \/dev\/csi-camera\n<\/pre>\n<p>If it points to the actual device like \/dev\/csi-camera -&gt; video2, it is successful.<\/p>\n<h2>Configuring Stable Systemd Service Startup Using the Fixed Device<\/h2>\n<p>We utilize the generated \/dev\/csi-camera from the uStreamer service. In addition to simply changing the path, configuring systemd&#8217;s startup order control (waiting for the device) prevents races where the service starts up before the camera device is recognized and fails.<\/p>\n<h3>Checking the Systemd Device Unit Name<\/h3>\n<p>Check the escaped device unit name handled by systemd.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">systemd-escape --path --suffix=device \/dev\/csi-camera<\/pre>\n<p>Output result:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">dev-csi\\x2dcamera.device<\/pre>\n<p>Updating the Service Definition Edit the service configuration file to specify the fixed path and add dependencies.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">rwnano \/etc\/systemd\/system\/ustreamer-csi.service<\/pre>\n<p>The contents of the configuration file \/etc\/systemd\/system\/ustreamer-csi.service are as follows:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;Unit]\nDescription=uStreamer for CSI Camera\nRequires=dev-csi\\x2dcamera.device\nAfter=network.target dev-csi\\x2dcamera.device\n\n&#x5B;Service]\nExecStart=\/usr\/bin\/ustreamer --device=\/dev\/csi-camera --host=0.0.0.0 --port=8081\nRestart=always\nRestartSec=3\nUser=root\n\n&#x5B;Install]\nWantedBy=multi-user.target\n<\/pre>\n<p>By specifying dev-csi\\x2dcamera.device in Requires and After, the service is guaranteed to start at the timing when udev completes the device link generation. Apply the settings, restart the service, and return the file system to read-only.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsystemctl daemon-reload\nsystemctl restart ustreamer-csi\nro\n<\/pre>\n<p>Ensuring Reliable Streaming via \/dev\/csi-camera and Applicability to Other Devices After completing the configuration, we checked uStreamer&#8217;s streaming status again.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">curl -s http:\/\/127.0.0.1:8081\/state<\/pre>\n<p>In the output, &#8220;online&#8221;: true was displayed, frame rates were counted normally, and we confirmed that the stream video was displayed seamlessly from the browser as well. After running multiple OS reboot tests, we verified that even if the CSI camera&#8217;s physical number fluctuated to video0 or video2, the service started 100% reliably via \/dev\/csi-camera. This approach of &#8220;generating a fixed symbolic link from unique conditions using udev, and controlling service startup order with TAG+=&#8221;systemd&#8221;&#8221; is applicable not only to cameras but to any devices prone to number fluctuations on Linux, such as USB-serial converters (\/dev\/ttyUSB*) or external storage.<\/p>\n<p><a href=\"https:\/\/amzn.to\/4xtnvoC\" target=\"_blank\" rel=\"noopener\" data-blogcard=\"1\">USB Power Splitter RPi Development Board BliKVM and PiKVM KVM Over IP Development Board Distributor Replacement Educational Institution<\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>We addressed the issue where the CSI camera&#8217;s device number fluctuates upon PiKVM reboot, causing uStreamer streaming to stop. By investigating attributes with udevadm to identify conditions including parent device information, we generated a fixed link \/dev\/csi-camera using udev rules. Furthermore, by linking it with systemd device dependencies (Requires), we established an environment where video streaming recovers automatically and reliably even after an OS reboot. This method is applicable to all kinds of Linux devices such as USB serial ports.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a PiKVM environment built on a Raspberry Pi 3 Model B, we introduced a CSI-connected camera module to exter [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":5422,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/donguri3.net\/?p=5396","footnotes":""},"categories":[1170],"tags":[105,461,462,10,275],"class_list":["post-5668","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-linux-server-network","tag-linux","tag-pikvm","tag-raspberry-pi-3","tag-server","tag-275","en-US"],"_links":{"self":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5668","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/comments?post=5668"}],"version-history":[{"count":1,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5668\/revisions"}],"predecessor-version":[{"id":5671,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5668\/revisions\/5671"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media\/5422"}],"wp:attachment":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media?parent=5668"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/categories?post=5668"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/tags?post=5668"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}