⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/drivers/
v4l2.rs

1//! V4L2 (Video for Linux 2) Device Interface
2//!
3//! Provides a kernel-side V4L2-compatible device interface for video
4//! capture devices (webcams, capture cards).  Implements the standard
5//! V4L2 ioctl commands and a test pattern generator.
6//!
7//! All color math uses integer-only operations (no floating point).
8
9#![allow(dead_code)]
10
11#[cfg(feature = "alloc")]
12extern crate alloc;
13
14use core::sync::atomic::{AtomicBool, Ordering};
15
16use spin::Mutex;
17
18use crate::error::KernelError;
19
20// ---------------------------------------------------------------------------
21// V4L2 ioctl command numbers
22// ---------------------------------------------------------------------------
23
24/// V4L2 ioctl base
25const VIDIOC_BASE: u32 = 0x5600;
26
27/// Query device capabilities
28const VIDIOC_QUERYCAP: u32 = VIDIOC_BASE;
29/// Enumerate image formats
30const VIDIOC_ENUM_FMT: u32 = VIDIOC_BASE + 0x02;
31/// Get current format
32const VIDIOC_G_FMT: u32 = VIDIOC_BASE + 0x04;
33/// Set format
34const VIDIOC_S_FMT: u32 = VIDIOC_BASE + 0x05;
35/// Request buffers
36const VIDIOC_REQBUFS: u32 = VIDIOC_BASE + 0x08;
37/// Query buffer status
38const VIDIOC_QUERYBUF: u32 = VIDIOC_BASE + 0x09;
39/// Queue buffer for capture
40const VIDIOC_QBUF: u32 = VIDIOC_BASE + 0x0F;
41/// Dequeue filled buffer
42const VIDIOC_DQBUF: u32 = VIDIOC_BASE + 0x11;
43/// Start streaming
44const VIDIOC_STREAMON: u32 = VIDIOC_BASE + 0x12;
45/// Stop streaming
46const VIDIOC_STREAMOFF: u32 = VIDIOC_BASE + 0x13;
47
48// ---------------------------------------------------------------------------
49// V4L2 pixel format FourCC codes
50// ---------------------------------------------------------------------------
51
52/// YUYV 4:2:2 packed (commonly used by USB webcams)
53const V4L2_PIX_FMT_YUYV: u32 = fourcc(b'Y', b'U', b'Y', b'V');
54/// RGB24 packed
55const V4L2_PIX_FMT_RGB24: u32 = fourcc(b'R', b'G', b'B', b'3');
56/// BGR24 packed
57const V4L2_PIX_FMT_BGR24: u32 = fourcc(b'B', b'G', b'R', b'3');
58/// MJPEG compressed
59const V4L2_PIX_FMT_MJPEG: u32 = fourcc(b'M', b'J', b'P', b'G');
60
61/// Create a FourCC value from 4 bytes
62const fn fourcc(a: u8, b: u8, c: u8, d: u8) -> u32 {
63    (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
64}
65
66// ---------------------------------------------------------------------------
67// V4L2 capability flags
68// ---------------------------------------------------------------------------
69
70/// Device supports video capture
71const V4L2_CAP_VIDEO_CAPTURE: u32 = 0x0000_0001;
72/// Device supports streaming I/O
73const V4L2_CAP_STREAMING: u32 = 0x0400_0000;
74/// Device supports read/write I/O
75const V4L2_CAP_READWRITE: u32 = 0x0100_0000;
76
77// ---------------------------------------------------------------------------
78// V4L2 buffer flags
79// ---------------------------------------------------------------------------
80
81/// Buffer is mapped into user space
82const V4L2_BUF_FLAG_MAPPED: u32 = 0x0001;
83/// Buffer is queued for input
84const V4L2_BUF_FLAG_QUEUED: u32 = 0x0002;
85/// Buffer has been filled with data
86const V4L2_BUF_FLAG_DONE: u32 = 0x0004;
87
88// ---------------------------------------------------------------------------
89// Constants
90// ---------------------------------------------------------------------------
91
92/// Maximum number of buffers in the pool
93const MAX_BUFFERS: usize = 4;
94/// Default capture width
95const DEFAULT_WIDTH: u32 = 640;
96/// Default capture height
97const DEFAULT_HEIGHT: u32 = 480;
98/// YUYV bytes per line: width * 2 (each pixel is 2 bytes in YUYV 4:2:2)
99const YUYV_BYTES_PER_LINE: u32 = DEFAULT_WIDTH * 2;
100/// YUYV frame size in bytes
101const YUYV_FRAME_SIZE: u32 = YUYV_BYTES_PER_LINE * DEFAULT_HEIGHT;
102/// Maximum driver name length
103const MAX_DRIVER_NAME: usize = 16;
104/// Maximum card name length
105const MAX_CARD_NAME: usize = 32;
106/// Maximum bus info length
107const MAX_BUS_INFO: usize = 32;
108
109/// Number of color bars in the test pattern
110const NUM_COLOR_BARS: usize = 8;
111
112// ---------------------------------------------------------------------------
113// Types
114// ---------------------------------------------------------------------------
115
116/// V4L2 device capabilities
117#[derive(Debug, Clone)]
118pub struct V4l2Capability {
119    /// Driver name (e.g., "veridian-v4l2")
120    pub driver: [u8; MAX_DRIVER_NAME],
121    /// Card/device name
122    pub card: [u8; MAX_CARD_NAME],
123    /// Bus location info
124    pub bus_info: [u8; MAX_BUS_INFO],
125    /// Kernel version
126    pub version: u32,
127    /// Capability flags
128    pub capabilities: u32,
129    /// Device capabilities (for multi-function devices)
130    pub device_caps: u32,
131}
132
133impl Default for V4l2Capability {
134    fn default() -> Self {
135        let mut cap = Self {
136            driver: [0u8; MAX_DRIVER_NAME],
137            card: [0u8; MAX_CARD_NAME],
138            bus_info: [0u8; MAX_BUS_INFO],
139            version: 0x0016_0000, // v0.22.0
140            capabilities: V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE,
141            device_caps: V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING,
142        };
143        copy_str_to_buf(&mut cap.driver, b"veridian-v4l2");
144        copy_str_to_buf(&mut cap.card, b"VeridianOS Virtual Camera");
145        copy_str_to_buf(&mut cap.bus_info, b"platform:veridian-v4l2");
146        cap
147    }
148}
149
150/// V4L2 pixel format descriptor
151#[derive(Debug, Clone, Copy)]
152pub struct V4l2PixFormat {
153    /// Image width in pixels
154    pub width: u32,
155    /// Image height in pixels
156    pub height: u32,
157    /// FourCC pixel format code
158    pub pixelformat: u32,
159    /// Bytes per line
160    pub bytesperline: u32,
161    /// Total image size in bytes
162    pub sizeimage: u32,
163}
164
165impl Default for V4l2PixFormat {
166    fn default() -> Self {
167        Self {
168            width: DEFAULT_WIDTH,
169            height: DEFAULT_HEIGHT,
170            pixelformat: V4L2_PIX_FMT_YUYV,
171            bytesperline: YUYV_BYTES_PER_LINE,
172            sizeimage: YUYV_FRAME_SIZE,
173        }
174    }
175}
176
177/// V4L2 format description (for enumeration)
178#[derive(Debug, Clone, Copy)]
179pub struct V4l2FmtDesc {
180    /// Format index
181    pub index: u32,
182    /// FourCC pixel format
183    pub pixelformat: u32,
184    /// Description string
185    pub description: [u8; 32],
186    /// Flags
187    pub flags: u32,
188}
189
190/// V4L2 buffer state
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum V4l2BufState {
193    /// Buffer is available (dequeued)
194    Idle,
195    /// Buffer is queued for capture
196    Queued,
197    /// Buffer has been filled with data
198    Done,
199}
200
201/// V4L2 buffer descriptor
202#[derive(Debug, Clone, Copy)]
203pub struct V4l2Buffer {
204    /// Buffer index
205    pub index: u32,
206    /// Buffer state
207    pub state: V4l2BufState,
208    /// Buffer flags
209    pub flags: u32,
210    /// Data offset in shared buffer pool
211    pub offset: u32,
212    /// Bytes used in this buffer
213    pub bytesused: u32,
214    /// Frame sequence number
215    pub sequence: u32,
216}
217
218impl Default for V4l2Buffer {
219    fn default() -> Self {
220        Self {
221            index: 0,
222            state: V4l2BufState::Idle,
223            flags: 0,
224            offset: 0,
225            bytesused: 0,
226            sequence: 0,
227        }
228    }
229}
230
231/// V4L2 device instance
232pub struct V4l2Device {
233    /// Device capabilities
234    capability: V4l2Capability,
235    /// Current pixel format
236    format: V4l2PixFormat,
237    /// Buffer descriptors
238    buffers: [V4l2Buffer; MAX_BUFFERS],
239    /// Number of allocated buffers
240    num_buffers: u32,
241    /// Whether the device is streaming
242    streaming: bool,
243    /// Frame counter (monotonically increasing)
244    frame_counter: u32,
245    /// Queue head index (next buffer to fill)
246    queue_head: usize,
247    /// Queue tail index (next buffer to dequeue)
248    queue_tail: usize,
249    /// Number of buffers currently queued
250    queued_count: usize,
251}
252
253impl Default for V4l2Device {
254    fn default() -> Self {
255        Self::new()
256    }
257}
258
259impl V4l2Device {
260    /// Create a new V4L2 device
261    pub const fn new() -> Self {
262        const DEFAULT_BUF: V4l2Buffer = V4l2Buffer {
263            index: 0,
264            state: V4l2BufState::Idle,
265            flags: 0,
266            offset: 0,
267            bytesused: 0,
268            sequence: 0,
269        };
270        Self {
271            capability: V4l2Capability {
272                driver: [0u8; MAX_DRIVER_NAME],
273                card: [0u8; MAX_CARD_NAME],
274                bus_info: [0u8; MAX_BUS_INFO],
275                version: 0x0016_0000,
276                capabilities: V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE,
277                device_caps: V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING,
278            },
279            format: V4l2PixFormat {
280                width: DEFAULT_WIDTH,
281                height: DEFAULT_HEIGHT,
282                pixelformat: V4L2_PIX_FMT_YUYV,
283                bytesperline: YUYV_BYTES_PER_LINE,
284                sizeimage: YUYV_FRAME_SIZE,
285            },
286            buffers: [DEFAULT_BUF; MAX_BUFFERS],
287            num_buffers: 0,
288            streaming: false,
289            frame_counter: 0,
290            queue_head: 0,
291            queue_tail: 0,
292            queued_count: 0,
293        }
294    }
295
296    /// Initialize the device
297    pub fn init(&mut self) {
298        self.capability = V4l2Capability::default();
299        self.format = V4l2PixFormat::default();
300        self.frame_counter = 0;
301        self.streaming = false;
302        self.num_buffers = 0;
303        self.queue_head = 0;
304        self.queue_tail = 0;
305        self.queued_count = 0;
306    }
307
308    /// Handle VIDIOC_QUERYCAP
309    pub fn query_cap(&self) -> V4l2Capability {
310        self.capability.clone()
311    }
312
313    /// Handle VIDIOC_ENUM_FMT
314    pub fn enum_fmt(&self, index: u32) -> Option<V4l2FmtDesc> {
315        match index {
316            0 => {
317                let mut desc = V4l2FmtDesc {
318                    index: 0,
319                    pixelformat: V4L2_PIX_FMT_YUYV,
320                    description: [0u8; 32],
321                    flags: 0,
322                };
323                copy_str_to_buf(&mut desc.description, b"YUYV 4:2:2");
324                Some(desc)
325            }
326            1 => {
327                let mut desc = V4l2FmtDesc {
328                    index: 1,
329                    pixelformat: V4L2_PIX_FMT_RGB24,
330                    description: [0u8; 32],
331                    flags: 0,
332                };
333                copy_str_to_buf(&mut desc.description, b"RGB24");
334                Some(desc)
335            }
336            _ => None,
337        }
338    }
339
340    /// Handle VIDIOC_G_FMT
341    pub fn get_format(&self) -> V4l2PixFormat {
342        self.format
343    }
344
345    /// Handle VIDIOC_S_FMT
346    pub fn set_format(&mut self, fmt: V4l2PixFormat) -> Result<V4l2PixFormat, KernelError> {
347        if self.streaming {
348            return Err(KernelError::InvalidState {
349                expected: "idle",
350                actual: "busy",
351            });
352        }
353
354        // Validate and clamp dimensions
355        let width = clamp(fmt.width, 160, 1920);
356        let height = clamp(fmt.height, 120, 1080);
357
358        // Only YUYV is fully supported for now
359        let pixelformat = if fmt.pixelformat == V4L2_PIX_FMT_RGB24 {
360            V4L2_PIX_FMT_RGB24
361        } else {
362            V4L2_PIX_FMT_YUYV
363        };
364
365        let bytesperline = if pixelformat == V4L2_PIX_FMT_YUYV {
366            width.checked_mul(2).unwrap_or(width)
367        } else {
368            width.checked_mul(3).unwrap_or(width)
369        };
370
371        let sizeimage = bytesperline.checked_mul(height).unwrap_or(bytesperline);
372
373        self.format = V4l2PixFormat {
374            width,
375            height,
376            pixelformat,
377            bytesperline,
378            sizeimage,
379        };
380
381        Ok(self.format)
382    }
383
384    /// Handle VIDIOC_REQBUFS
385    pub fn request_buffers(&mut self, count: u32) -> Result<u32, KernelError> {
386        if self.streaming {
387            return Err(KernelError::InvalidState {
388                expected: "idle",
389                actual: "busy",
390            });
391        }
392
393        let actual = if count > MAX_BUFFERS as u32 {
394            MAX_BUFFERS as u32
395        } else {
396            count
397        };
398
399        self.num_buffers = actual;
400        for i in 0..actual as usize {
401            self.buffers[i] = V4l2Buffer {
402                index: i as u32,
403                state: V4l2BufState::Idle,
404                flags: 0,
405                offset: (i as u32).checked_mul(self.format.sizeimage).unwrap_or(0),
406                bytesused: 0,
407                sequence: 0,
408            };
409        }
410
411        self.queue_head = 0;
412        self.queue_tail = 0;
413        self.queued_count = 0;
414
415        Ok(actual)
416    }
417
418    /// Handle VIDIOC_QUERYBUF
419    pub fn query_buffer(&self, index: u32) -> Option<V4l2Buffer> {
420        if index < self.num_buffers {
421            Some(self.buffers[index as usize])
422        } else {
423            None
424        }
425    }
426
427    /// Handle VIDIOC_QBUF -- queue a buffer for capture
428    pub fn queue_buffer(&mut self, index: u32) -> Result<(), KernelError> {
429        if index >= self.num_buffers {
430            return Err(KernelError::InvalidArgument {
431                name: "v4l2",
432                value: "invalid",
433            });
434        }
435
436        let buf = &mut self.buffers[index as usize];
437        if buf.state != V4l2BufState::Idle {
438            return Err(KernelError::InvalidState {
439                expected: "idle",
440                actual: "busy",
441            });
442        }
443
444        buf.state = V4l2BufState::Queued;
445        buf.flags = V4L2_BUF_FLAG_QUEUED;
446        self.queued_count += 1;
447        Ok(())
448    }
449
450    /// Handle VIDIOC_DQBUF -- dequeue a filled buffer
451    pub fn dequeue_buffer(&mut self) -> Result<V4l2Buffer, KernelError> {
452        if !self.streaming {
453            return Err(KernelError::InvalidState {
454                expected: "idle",
455                actual: "busy",
456            });
457        }
458
459        // Find the next buffer marked as Done
460        for i in 0..self.num_buffers as usize {
461            if self.buffers[i].state == V4l2BufState::Done {
462                self.buffers[i].state = V4l2BufState::Idle;
463                self.buffers[i].flags = 0;
464                let result = self.buffers[i];
465                return Ok(result);
466            }
467        }
468
469        // No buffers ready; in a real driver we'd block or return EAGAIN
470        Err(KernelError::WouldBlock)
471    }
472
473    /// Handle VIDIOC_STREAMON
474    pub fn stream_on(&mut self) -> Result<(), KernelError> {
475        if self.streaming {
476            return Err(KernelError::InvalidState {
477                expected: "idle",
478                actual: "busy",
479            });
480        }
481        if self.num_buffers == 0 {
482            return Err(KernelError::InvalidArgument {
483                name: "v4l2",
484                value: "invalid",
485            });
486        }
487        self.streaming = true;
488        self.frame_counter = 0;
489        Ok(())
490    }
491
492    /// Handle VIDIOC_STREAMOFF
493    pub fn stream_off(&mut self) -> Result<(), KernelError> {
494        self.streaming = false;
495
496        // Return all buffers to idle
497        for i in 0..self.num_buffers as usize {
498            self.buffers[i].state = V4l2BufState::Idle;
499            self.buffers[i].flags = 0;
500        }
501        self.queued_count = 0;
502        Ok(())
503    }
504
505    /// Generate a test frame into a queued buffer
506    ///
507    /// Produces SMPTE-style color bars in YUYV 4:2:2 format.
508    /// Called by the capture loop to fill queued buffers.
509    pub fn generate_test_frame(&mut self, output: &mut [u8]) -> Result<usize, KernelError> {
510        if !self.streaming {
511            return Err(KernelError::InvalidState {
512                expected: "idle",
513                actual: "busy",
514            });
515        }
516
517        // Find a queued buffer
518        let mut buf_idx = None;
519        for i in 0..self.num_buffers as usize {
520            if self.buffers[i].state == V4l2BufState::Queued {
521                buf_idx = Some(i);
522                break;
523            }
524        }
525
526        let idx = buf_idx.ok_or(KernelError::WouldBlock)?;
527
528        let width = self.format.width as usize;
529        let height = self.format.height as usize;
530        let frame_size = width * height * 2; // YUYV: 2 bytes per pixel
531
532        if output.len() < frame_size {
533            return Err(KernelError::ResourceExhausted {
534                resource: "v4l2 buffer",
535            });
536        }
537
538        // Generate SMPTE color bars in YUYV format
539        // 8 bars: white, yellow, cyan, green, magenta, red, blue, black
540        generate_color_bars_yuyv(output, width, height, self.frame_counter);
541
542        // Mark buffer as done
543        self.buffers[idx].state = V4l2BufState::Done;
544        self.buffers[idx].flags = V4L2_BUF_FLAG_DONE;
545        self.buffers[idx].bytesused = frame_size as u32;
546        self.buffers[idx].sequence = self.frame_counter;
547        self.frame_counter = self.frame_counter.wrapping_add(1);
548
549        if self.queued_count > 0 {
550            self.queued_count -= 1;
551        }
552
553        Ok(frame_size)
554    }
555
556    /// Check if the device is currently streaming
557    pub fn is_streaming(&self) -> bool {
558        self.streaming
559    }
560
561    /// Get the current frame counter
562    pub fn frame_counter(&self) -> u32 {
563        self.frame_counter
564    }
565
566    /// Get the number of allocated buffers
567    pub fn num_buffers(&self) -> u32 {
568        self.num_buffers
569    }
570}
571
572// ---------------------------------------------------------------------------
573// Test Pattern Generator
574// ---------------------------------------------------------------------------
575
576/// SMPTE color bar RGB values
577/// Order: white, yellow, cyan, green, magenta, red, blue, black
578const COLOR_BAR_RGB: [(u8, u8, u8); NUM_COLOR_BARS] = [
579    (255, 255, 255), // White
580    (255, 255, 0),   // Yellow
581    (0, 255, 255),   // Cyan
582    (0, 255, 0),     // Green
583    (255, 0, 255),   // Magenta
584    (255, 0, 0),     // Red
585    (0, 0, 255),     // Blue
586    (0, 0, 0),       // Black
587];
588
589/// Convert RGB to Y (luminance) using integer math
590///
591/// Formula: Y = (66*R + 129*G + 25*B + 128) >> 8 + 16
592/// This gives values in [16, 235] per ITU-R BT.601.
593fn rgb_to_y(r: u8, g: u8, b: u8) -> u8 {
594    let r32 = r as u32;
595    let g32 = g as u32;
596    let b32 = b as u32;
597    let y = ((66u32
598        .checked_mul(r32)
599        .unwrap_or(0)
600        .checked_add(129u32.checked_mul(g32).unwrap_or(0))
601        .unwrap_or(0)
602        .checked_add(25u32.checked_mul(b32).unwrap_or(0))
603        .unwrap_or(0)
604        .checked_add(128)
605        .unwrap_or(0))
606        >> 8)
607        .checked_add(16)
608        .unwrap_or(16);
609    if y > 255 {
610        255u8
611    } else {
612        y as u8
613    }
614}
615
616/// Convert RGB to U (Cb) chrominance using integer math
617///
618/// Formula: U = (-38*R - 74*G + 112*B + 128) >> 8 + 128
619fn rgb_to_u(r: u8, g: u8, b: u8) -> u8 {
620    let r32 = r as i32;
621    let g32 = g as i32;
622    let b32 = b as i32;
623    let u = ((-38i32 * r32 - 74i32 * g32 + 112i32 * b32 + 128i32) >> 8) + 128i32;
624    if u < 0 {
625        0u8
626    } else if u > 255 {
627        255u8
628    } else {
629        u as u8
630    }
631}
632
633/// Convert RGB to V (Cr) chrominance using integer math
634///
635/// Formula: V = (112*R - 94*G - 18*B + 128) >> 8 + 128
636fn rgb_to_v(r: u8, g: u8, b: u8) -> u8 {
637    let r32 = r as i32;
638    let g32 = g as i32;
639    let b32 = b as i32;
640    let v = ((112i32 * r32 - 94i32 * g32 - 18i32 * b32 + 128i32) >> 8) + 128i32;
641    if v < 0 {
642        0u8
643    } else if v > 255 {
644        255u8
645    } else {
646        v as u8
647    }
648}
649
650/// Generate SMPTE color bars in YUYV 4:2:2 format
651///
652/// YUYV packing: [Y0, U01, Y1, V01] for each pair of horizontal pixels.
653fn generate_color_bars_yuyv(output: &mut [u8], width: usize, height: usize, frame_num: u32) {
654    let bar_width = width / NUM_COLOR_BARS;
655
656    for y in 0..height {
657        let row_offset = y * width * 2;
658
659        // Process pixels in pairs (YUYV = 2 pixels per 4 bytes)
660        let mut x = 0usize;
661        while x < width {
662            let bar0 = if bar_width > 0 { x / bar_width } else { 0 };
663            let bar1 = if bar_width > 0 {
664                (x + 1) / bar_width
665            } else {
666                0
667            };
668            let bar0 = if bar0 >= NUM_COLOR_BARS {
669                NUM_COLOR_BARS - 1
670            } else {
671                bar0
672            };
673            let bar1 = if bar1 >= NUM_COLOR_BARS {
674                NUM_COLOR_BARS - 1
675            } else {
676                bar1
677            };
678
679            let (r0, g0, b0) = COLOR_BAR_RGB[bar0];
680            let (r1, g1, b1) = COLOR_BAR_RGB[bar1];
681
682            let y0 = rgb_to_y(r0, g0, b0);
683            let y1 = rgb_to_y(r1, g1, b1);
684            // Average the U/V of the two pixels in the pair
685            let u = rgb_to_u(
686                ((r0 as u16 + r1 as u16) / 2) as u8,
687                ((g0 as u16 + g1 as u16) / 2) as u8,
688                ((b0 as u16 + b1 as u16) / 2) as u8,
689            );
690            let v = rgb_to_v(
691                ((r0 as u16 + r1 as u16) / 2) as u8,
692                ((g0 as u16 + g1 as u16) / 2) as u8,
693                ((b0 as u16 + b1 as u16) / 2) as u8,
694            );
695
696            let offset = row_offset + x * 2;
697            if offset + 3 < output.len() {
698                output[offset] = y0;
699                output[offset + 1] = u;
700                output[offset + 2] = y1;
701                output[offset + 3] = v;
702            }
703
704            x += 2;
705        }
706    }
707
708    // Overlay frame counter in top-left corner (8x8 block, simple)
709    // Toggle between bright and dark based on frame number for visibility
710    let marker_val = if frame_num & 1 == 0 { 235u8 } else { 16u8 };
711    for py in 0..8usize {
712        if py >= height {
713            break;
714        }
715        for px in 0..8usize {
716            if px >= width {
717                break;
718            }
719            let offset = py * width * 2 + px * 2;
720            if offset < output.len() {
721                output[offset] = marker_val;
722            }
723        }
724    }
725}
726
727// ---------------------------------------------------------------------------
728// V4L2 ioctl dispatcher
729// ---------------------------------------------------------------------------
730
731/// Process a V4L2 ioctl command
732pub fn v4l2_ioctl(device: &mut V4l2Device, cmd: u32, arg: u64) -> Result<u64, KernelError> {
733    match cmd {
734        VIDIOC_QUERYCAP => {
735            let _cap = device.query_cap();
736            Ok(0)
737        }
738        VIDIOC_ENUM_FMT => {
739            let index = arg as u32;
740            match device.enum_fmt(index) {
741                Some(_desc) => Ok(0),
742                None => Err(KernelError::InvalidArgument {
743                    name: "v4l2",
744                    value: "invalid",
745                }),
746            }
747        }
748        VIDIOC_G_FMT => {
749            let _fmt = device.get_format();
750            Ok(0)
751        }
752        VIDIOC_S_FMT => {
753            // In a real implementation, arg would point to a user-space format struct
754            let fmt = V4l2PixFormat {
755                width: (arg & 0xFFFF) as u32,
756                height: ((arg >> 16) & 0xFFFF) as u32,
757                pixelformat: V4L2_PIX_FMT_YUYV,
758                bytesperline: 0, // will be computed
759                sizeimage: 0,    // will be computed
760            };
761            device.set_format(fmt)?;
762            Ok(0)
763        }
764        VIDIOC_REQBUFS => {
765            let count = arg as u32;
766            let actual = device.request_buffers(count)?;
767            Ok(actual as u64)
768        }
769        VIDIOC_QUERYBUF => {
770            let index = arg as u32;
771            match device.query_buffer(index) {
772                Some(_buf) => Ok(0),
773                None => Err(KernelError::InvalidArgument {
774                    name: "v4l2",
775                    value: "invalid",
776                }),
777            }
778        }
779        VIDIOC_QBUF => {
780            let index = arg as u32;
781            device.queue_buffer(index)?;
782            Ok(0)
783        }
784        VIDIOC_DQBUF => {
785            let buf = device.dequeue_buffer()?;
786            Ok(buf.index as u64)
787        }
788        VIDIOC_STREAMON => {
789            device.stream_on()?;
790            Ok(0)
791        }
792        VIDIOC_STREAMOFF => {
793            device.stream_off()?;
794            Ok(0)
795        }
796        _ => Err(KernelError::InvalidArgument {
797            name: "v4l2",
798            value: "invalid",
799        }),
800    }
801}
802
803// ---------------------------------------------------------------------------
804// Global V4L2 device
805// ---------------------------------------------------------------------------
806
807static V4L2_DEVICE: Mutex<V4l2Device> = Mutex::new(V4l2Device::new());
808
809static V4L2_INITIALIZED: AtomicBool = AtomicBool::new(false);
810
811/// Initialize the V4L2 subsystem
812pub fn v4l2_init() {
813    let mut dev = V4L2_DEVICE.lock();
814    dev.init();
815    V4L2_INITIALIZED.store(true, Ordering::Release);
816    crate::println!("[V4L2] Virtual camera device initialized (640x480 YUYV)");
817}
818
819/// Process a V4L2 ioctl on the global device
820pub fn v4l2_global_ioctl(cmd: u32, arg: u64) -> Result<u64, KernelError> {
821    if !V4L2_INITIALIZED.load(Ordering::Acquire) {
822        return Err(KernelError::NotInitialized { subsystem: "v4l2" });
823    }
824    let mut dev = V4L2_DEVICE.lock();
825    v4l2_ioctl(&mut dev, cmd, arg)
826}
827
828// ---------------------------------------------------------------------------
829// Helpers
830// ---------------------------------------------------------------------------
831
832/// Copy a byte string into a fixed-size buffer, null-terminating
833fn copy_str_to_buf(buf: &mut [u8], src: &[u8]) {
834    let len = if src.len() < buf.len() - 1 {
835        src.len()
836    } else {
837        buf.len() - 1
838    };
839    buf[..len].copy_from_slice(&src[..len]);
840    buf[len] = 0;
841}
842
843/// Clamp a value to a range
844fn clamp(val: u32, min: u32, max: u32) -> u32 {
845    if val < min {
846        min
847    } else if val > max {
848        max
849    } else {
850        val
851    }
852}
853
854// ---------------------------------------------------------------------------
855// Tests
856// ---------------------------------------------------------------------------
857
858#[cfg(test)]
859mod tests {
860    use super::*;
861
862    #[test]
863    fn test_fourcc() {
864        let yuyv = fourcc(b'Y', b'U', b'Y', b'V');
865        assert_ne!(yuyv, 0);
866        // Check byte order
867        assert_eq!(yuyv & 0xFF, b'Y' as u32);
868        assert_eq!((yuyv >> 8) & 0xFF, b'U' as u32);
869    }
870
871    #[test]
872    fn test_rgb_to_y() {
873        // White should give high Y
874        let y_white = rgb_to_y(255, 255, 255);
875        assert!(y_white > 200);
876
877        // Black should give low Y
878        let y_black = rgb_to_y(0, 0, 0);
879        assert!(y_black < 30);
880
881        // Y should be in BT.601 range
882        assert!(y_white <= 255);
883        assert!(y_black >= 16);
884    }
885
886    #[test]
887    fn test_rgb_to_u() {
888        // For neutral gray, U should be ~128
889        let u_gray = rgb_to_u(128, 128, 128);
890        assert!((u_gray as i32 - 128).unsigned_abs() < 5);
891    }
892
893    #[test]
894    fn test_rgb_to_v() {
895        // For neutral gray, V should be ~128
896        let v_gray = rgb_to_v(128, 128, 128);
897        assert!((v_gray as i32 - 128).unsigned_abs() < 5);
898    }
899
900    #[test]
901    fn test_v4l2_capability_default() {
902        let cap = V4l2Capability::default();
903        assert!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE != 0);
904        assert!(cap.capabilities & V4L2_CAP_STREAMING != 0);
905        assert_eq!(cap.driver[0], b'v');
906    }
907
908    #[test]
909    fn test_v4l2_device_new() {
910        let dev = V4l2Device::new();
911        assert!(!dev.is_streaming());
912        assert_eq!(dev.frame_counter(), 0);
913        assert_eq!(dev.num_buffers(), 0);
914    }
915
916    #[test]
917    fn test_v4l2_device_init() {
918        let mut dev = V4l2Device::new();
919        dev.init();
920        let fmt = dev.get_format();
921        assert_eq!(fmt.width, DEFAULT_WIDTH);
922        assert_eq!(fmt.height, DEFAULT_HEIGHT);
923        assert_eq!(fmt.pixelformat, V4L2_PIX_FMT_YUYV);
924    }
925
926    #[test]
927    fn test_v4l2_enum_fmt() {
928        let mut dev = V4l2Device::new();
929        dev.init();
930
931        assert!(dev.enum_fmt(0).is_some());
932        assert!(dev.enum_fmt(1).is_some());
933        assert!(dev.enum_fmt(2).is_none());
934    }
935
936    #[test]
937    fn test_v4l2_set_format() {
938        let mut dev = V4l2Device::new();
939        dev.init();
940
941        let fmt = V4l2PixFormat {
942            width: 320,
943            height: 240,
944            pixelformat: V4L2_PIX_FMT_YUYV,
945            bytesperline: 0,
946            sizeimage: 0,
947        };
948        let result = dev.set_format(fmt);
949        assert!(result.is_ok());
950
951        let actual = result.unwrap();
952        assert_eq!(actual.width, 320);
953        assert_eq!(actual.height, 240);
954        assert_eq!(actual.bytesperline, 640); // 320 * 2
955    }
956
957    #[test]
958    fn test_v4l2_set_format_clamping() {
959        let mut dev = V4l2Device::new();
960        dev.init();
961
962        let fmt = V4l2PixFormat {
963            width: 10000,
964            height: 10000,
965            pixelformat: V4L2_PIX_FMT_YUYV,
966            bytesperline: 0,
967            sizeimage: 0,
968        };
969        let result = dev.set_format(fmt).unwrap();
970        assert_eq!(result.width, 1920);
971        assert_eq!(result.height, 1080);
972    }
973
974    #[test]
975    fn test_v4l2_request_buffers() {
976        let mut dev = V4l2Device::new();
977        dev.init();
978
979        let count = dev.request_buffers(4).unwrap();
980        assert_eq!(count, 4);
981        assert_eq!(dev.num_buffers(), 4);
982    }
983
984    #[test]
985    fn test_v4l2_request_buffers_capped() {
986        let mut dev = V4l2Device::new();
987        dev.init();
988
989        let count = dev.request_buffers(100).unwrap();
990        assert_eq!(count, MAX_BUFFERS as u32);
991    }
992
993    #[test]
994    fn test_v4l2_query_buffer() {
995        let mut dev = V4l2Device::new();
996        dev.init();
997        dev.request_buffers(4).unwrap();
998
999        let buf = dev.query_buffer(0);
1000        assert!(buf.is_some());
1001        let buf = buf.unwrap();
1002        assert_eq!(buf.index, 0);
1003        assert_eq!(buf.state, V4l2BufState::Idle);
1004
1005        assert!(dev.query_buffer(4).is_none());
1006    }
1007
1008    #[test]
1009    fn test_v4l2_queue_dequeue() {
1010        let mut dev = V4l2Device::new();
1011        dev.init();
1012        dev.request_buffers(2).unwrap();
1013
1014        // Queue buffer 0
1015        assert!(dev.queue_buffer(0).is_ok());
1016
1017        // Can't dequeue while not streaming
1018        assert!(dev.dequeue_buffer().is_err());
1019
1020        // Start streaming
1021        assert!(dev.stream_on().is_ok());
1022
1023        // Generate frame into queued buffer
1024        let mut frame = [0u8; (DEFAULT_WIDTH * DEFAULT_HEIGHT * 2) as usize];
1025        assert!(dev.generate_test_frame(&mut frame).is_ok());
1026
1027        // Now dequeue
1028        let buf = dev.dequeue_buffer();
1029        assert!(buf.is_ok());
1030        assert_eq!(buf.unwrap().sequence, 0);
1031    }
1032
1033    #[test]
1034    fn test_v4l2_stream_on_off() {
1035        let mut dev = V4l2Device::new();
1036        dev.init();
1037        dev.request_buffers(2).unwrap();
1038
1039        assert!(dev.stream_on().is_ok());
1040        assert!(dev.is_streaming());
1041
1042        // Can't stream on twice
1043        assert!(dev.stream_on().is_err());
1044
1045        assert!(dev.stream_off().is_ok());
1046        assert!(!dev.is_streaming());
1047    }
1048
1049    #[test]
1050    fn test_v4l2_no_buffers_stream_on() {
1051        let mut dev = V4l2Device::new();
1052        dev.init();
1053
1054        // Can't stream without buffers
1055        assert!(dev.stream_on().is_err());
1056    }
1057
1058    #[test]
1059    fn test_v4l2_ioctl_dispatch() {
1060        let mut dev = V4l2Device::new();
1061        dev.init();
1062
1063        assert!(v4l2_ioctl(&mut dev, VIDIOC_QUERYCAP, 0).is_ok());
1064        assert!(v4l2_ioctl(&mut dev, VIDIOC_ENUM_FMT, 0).is_ok());
1065        assert!(v4l2_ioctl(&mut dev, VIDIOC_ENUM_FMT, 99).is_err());
1066        assert!(v4l2_ioctl(&mut dev, VIDIOC_G_FMT, 0).is_ok());
1067
1068        // Request buffers
1069        let result = v4l2_ioctl(&mut dev, VIDIOC_REQBUFS, 4);
1070        assert!(result.is_ok());
1071        assert_eq!(result.unwrap(), 4);
1072
1073        // Unknown ioctl
1074        assert!(v4l2_ioctl(&mut dev, 0xFFFF, 0).is_err());
1075    }
1076
1077    #[test]
1078    fn test_generate_color_bars() {
1079        let width = 64usize;
1080        let height = 4usize;
1081        let mut buf = [0u8; 64 * 4 * 2];
1082        generate_color_bars_yuyv(&mut buf, width, height, 0);
1083
1084        // First pixel should be from white bar (high Y)
1085        assert!(buf[0] > 200); // Y of white
1086    }
1087
1088    #[test]
1089    fn test_copy_str_to_buf() {
1090        let mut buf = [0xFFu8; 16];
1091        copy_str_to_buf(&mut buf, b"hello");
1092        assert_eq!(&buf[..5], b"hello");
1093        assert_eq!(buf[5], 0);
1094    }
1095
1096    #[test]
1097    fn test_clamp() {
1098        assert_eq!(clamp(50, 0, 100), 50);
1099        assert_eq!(clamp(0, 10, 100), 10);
1100        assert_eq!(clamp(200, 10, 100), 100);
1101    }
1102
1103    #[test]
1104    fn test_ioctl_constants() {
1105        assert_eq!(VIDIOC_QUERYCAP, 0x5600);
1106        assert_eq!(VIDIOC_ENUM_FMT, 0x5602);
1107        assert_eq!(VIDIOC_STREAMON, 0x5612);
1108        assert_eq!(VIDIOC_STREAMOFF, 0x5613);
1109    }
1110}