├── .ci └── check-format.sh ├── .clang-format ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── control.c ├── control.h ├── device.c ├── device.h ├── fb.c ├── fb.h ├── module.c ├── udev └── 66-vcmod.rules ├── vcam-util.c ├── vcam.h ├── videobuf.c └── videobuf.h /.ci/check-format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOURCES=$(find $(git rev-parse --show-toplevel) | egrep "\.(cpp|cc|c|h)\$") 4 | 5 | CLANG_FORMAT=$(which clang-format-18) 6 | if [ $? -ne 0 ]; then 7 | CLANG_FORMAT=$(which clang-format) 8 | if [ $? -ne 0 ]; then 9 | echo "[!] clang-format not installed. Unable to check source file format policy." >&2 10 | exit 1 11 | fi 12 | fi 13 | 14 | set -x 15 | 16 | for file in ${SOURCES}; 17 | do 18 | $CLANG_FORMAT ${file} > expected-format 19 | diff -u -p --label="${file}" --label="expected coding style" ${file} expected-format 20 | done 21 | exit $($CLANG_FORMAT --output-replacements-xml ${SOURCES} | egrep -c "") 22 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - foreach 18 | - Q_FOREACH 19 | - BOOST_FOREACH 20 | - list_for_each 21 | - list_for_each_safe 22 | - list_for_each_entry 23 | - list_for_each_entry_safe 24 | - hlist_for_each_entry 25 | - rb_list_foreach 26 | - rb_list_foreach_safe 27 | - vec_foreach 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | vcam: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout code 10 | uses: actions/checkout@v3.0.2 11 | - name: make 12 | run: make 13 | shell: bash 14 | 15 | coding-style: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3.0.2 19 | - name: coding convention 20 | run: | 21 | sudo apt-get install -q -y clang-format-18 22 | sh .ci/check-format.sh 23 | shell: bash 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | .cache.mk 4 | 5 | # Object files 6 | *.o 7 | *.ko 8 | *.elf 9 | 10 | # Linker output 11 | *.map 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Libraries 18 | *.a 19 | 20 | # Kernel Module Compile Results 21 | *.mod* 22 | *.cmd 23 | .tmp_versions/ 24 | modules.order 25 | Module.symvers 26 | Mkfile.old 27 | dkms.conf 28 | vcam-util 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2020-2022, 2024 National Cheng Kung University, Taiwan. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | target = vcam 2 | vcam-objs = module.o control.o device.o videobuf.o fb.o 3 | obj-m = $(target).o 4 | 5 | CFLAGS_utils = -O2 -Wall -Wextra -pedantic -std=c99 6 | 7 | .PHONY: all 8 | all: kmod vcam-util 9 | 10 | vcam-util: vcam-util.c vcam.h 11 | $(CC) $(CFLAGS_utils) -o $@ $< 12 | 13 | kmod: 14 | $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 15 | 16 | .PHONY: clean 17 | clean: 18 | $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 19 | $(RM) vcam-util 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vcam: Virtual camera device driver for Linux 2 | 3 | This Linux module implements a simplified virtual V4L2 compatible camera 4 | device driver with raw framebuffer input. 5 | 6 | ## Prerequisite 7 | 8 | The following packages must be installed before building `vcam`. 9 | 10 | In order to compile the kernel driver successfully, package versions 11 | of currently used kernel, kernel-devel and kernel-headers need to be matched. 12 | ```shell 13 | $ sudo apt install linux-headers-$(uname -r) 14 | ``` 15 | 16 | Since `vcam` is built with [V4L2](https://en.wikipedia.org/wiki/Video4Linux) (Video4Linux, second version), 17 | `v4l-utils` is necessary for retrieving more information and function validation: 18 | ```shell 19 | $ sudo apt install v4l-utils 20 | ``` 21 | 22 | ## Build and Run 23 | 24 | After running `make`, you should be able to generate the following files: 25 | * `vcam.ko` - Linux kernel module; 26 | * `vcam-util` - Sample utility to configure virtual camera device(s); 27 | 28 | Before loading this kernel module, you have to satisfy its dependency: 29 | ```shell 30 | $ sudo modprobe -a videobuf2_vmalloc videobuf2_v4l2 31 | ``` 32 | 33 | The module can be loaded to Linux kernel by runnning the command: 34 | ```shell 35 | $ sudo insmod vcam.ko 36 | ``` 37 | 38 | Expectedly, three device nodes will be created in `/dev`: 39 | * videoX - V4L2 device; 40 | * vcamctl - Control device for virtual camera(s), used by control utility `vcam-util`; 41 | * fbX - controlling framebuffer device; 42 | 43 | In `/dev` directory, device file `fbX` will be created. 44 | 45 | The device is initially configured to process 640x480 RGB24 image format. 46 | By writing 640x480 RGB24 raw frame data to `/dev/fbX` file the resulting 47 | video stream will appear on corresponding `/dev/videoX` V4L2 device(s). 48 | 49 | Run `vcam-util --help` for more information about how to configure, add or 50 | remove virtual camera devices. 51 | e.g. list all available virtual camera device(s): 52 | ```shell 53 | $ sudo ./vcam-util -l 54 | ``` 55 | 56 | You should get: 57 | ``` 58 | Available virtual V4L2 compatible devices: 59 | 1. fbX(640,480,rgb24) -> /dev/video0 60 | ``` 61 | 62 | You can use this command to check if the driver is ok: 63 | ```shell 64 | $ sudo v4l2-compliance -d /dev/videoX -f 65 | ``` 66 | 67 | It will return a bunch of test lines, with 0 failed and 0 warnings at the end. 68 | 69 | You can check if all configured formats and emulated controls are ok with this command: 70 | ```shell 71 | $ sudo v4l2-ctl -d /dev/videoX --all 72 | ``` 73 | 74 | You will get information as following: 75 | ``` 76 | Driver Info: 77 | Driver name : vcam 78 | Card type : vcam 79 | Bus info : platform: virtual 80 | Driver version: 4.15.18 81 | Capabilities : 0x85200001 82 | Video Capture 83 | Read/Write 84 | Streaming 85 | Extended Pix Format 86 | Device Capabilities 87 | ``` 88 | You can check framebuffer format with this command: 89 | ```shell 90 | $ sudo fbset -fb /dev/fbX --info 91 | ``` 92 | 93 | You will get information as following: 94 | ```shell 95 | mode "640x480" 96 | geometry 640 480 640 480 24 97 | timings 0 0 0 0 0 0 0 98 | rgba 8/0,8/8,8/16,0/0 99 | endmode 100 | 101 | Frame buffer device information: 102 | Name : vcamfb 103 | Address : 0xffffa293438ed000 104 | Size : 921600 105 | Type : PACKED PIXELS 106 | Visual : TRUECOLOR 107 | ``` 108 | 109 | Available parameters for `vcam` kernel module: 110 | * `devices_max` - Maximal number of devices. The default is 8. 111 | * `create_devices` - Number of devices to be created during initialization. The default is 1. 112 | * `allow_pix_conversion` - Allow pixel format conversion from RGB24 to YUYV. The default is OFF. 113 | * `allow_scaling` - Allow image scaling from 480p to 720p. The default is OFF. 114 | * `allow_cropping` - Allow image cropping in Four-Thirds system. The default is OFF. 115 | 116 | When you load a module using insmod command, you can supply the parameters as key=value pairs for example: 117 | ```shell 118 | $ sudo insmod vcam.ko allow_pix_conversion=1 119 | ``` 120 | 121 | ## Related Projects 122 | 123 | * [akvcam](https://github.com/webcamoid/akvcam) 124 | * [v4l2loopback](https://github.com/umlaeute/v4l2loopback) 125 | * [vivid: The Virtual Video Test Driver](https://www.kernel.org/doc/html/latest/media/v4l-drivers/vivid.html) 126 | 127 | ## License 128 | 129 | `vcam` is released under the MIT License. Use of this source code is governed by 130 | a MIT License that can be found in the LICENSE file. 131 | -------------------------------------------------------------------------------- /control.c: -------------------------------------------------------------------------------- 1 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "control.h" 11 | #include "device.h" 12 | #include "fb.h" 13 | #include "videobuf.h" 14 | 15 | extern unsigned short devices_max; 16 | 17 | struct control_device { 18 | int major; 19 | dev_t dev_number; 20 | struct class *dev_class; 21 | struct device *device; 22 | struct cdev cdev; 23 | struct vcam_device **vcam_devices; 24 | size_t vcam_device_count; 25 | spinlock_t vcam_devices_lock; 26 | }; 27 | 28 | static struct control_device *ctldev = NULL; 29 | 30 | static int control_open(struct inode *inode, struct file *file) 31 | { 32 | return 0; 33 | } 34 | 35 | static int control_release(struct inode *inode, struct file *file) 36 | { 37 | return 0; 38 | } 39 | 40 | static ssize_t control_read(struct file *file, 41 | char __user *buffer, 42 | size_t length, 43 | loff_t *offset) 44 | { 45 | int len; 46 | static const char *str = "Virtual V4L2 compatible camera device\n"; 47 | pr_debug("read %p %dB\n", buffer, (int) length); 48 | len = strlen(str); 49 | if (len < length) 50 | len = length; 51 | if (copy_to_user(buffer, str, len) != 0) 52 | pr_warn("Failed to copy_to_user!"); 53 | return len; 54 | } 55 | 56 | static ssize_t control_write(struct file *file, 57 | const char __user *buffer, 58 | size_t length, 59 | loff_t *offset) 60 | { 61 | pr_debug("write %p %dB\n", buffer, (int) length); 62 | return length; 63 | } 64 | 65 | static int control_iocontrol_get_device(struct vcam_device_spec *dev_spec) 66 | { 67 | struct vcam_device *dev; 68 | 69 | if (ctldev->vcam_device_count <= dev_spec->idx) 70 | return -EINVAL; 71 | 72 | dev = ctldev->vcam_devices[dev_spec->idx]; 73 | dev_spec->width = dev->fb_spec.xres_virtual; 74 | dev_spec->height = dev->fb_spec.yres_virtual; 75 | dev_spec->pix_fmt = dev->fb_spec.pix_fmt; 76 | dev_spec->cropratio = dev->fb_spec.cropratio; 77 | 78 | strncpy((char *) &dev_spec->fb_node, (const char *) vcamfb_get_devnode(dev), 79 | sizeof(dev_spec->fb_node)); 80 | snprintf((char *) &dev_spec->video_node, sizeof(dev_spec->video_node), 81 | "/dev/video%d", dev->vdev.num); 82 | return 0; 83 | } 84 | 85 | static int control_iocontrol_modify_input_setting( 86 | struct vcam_device_spec *dev_spec) 87 | { 88 | struct vcam_device *dev; 89 | int res; 90 | 91 | if (ctldev->vcam_device_count <= dev_spec->idx) 92 | return -EINVAL; 93 | 94 | dev = ctldev->vcam_devices[dev_spec->idx]; 95 | 96 | res = modify_vcam_device(dev, dev_spec); 97 | 98 | return res; 99 | } 100 | 101 | static int control_iocontrol_destroy_device(struct vcam_device_spec *dev_spec) 102 | { 103 | struct vcam_device *dev; 104 | unsigned long flags = 0; 105 | int i; 106 | 107 | if (ctldev->vcam_device_count <= dev_spec->idx) 108 | return -EINVAL; 109 | 110 | dev = ctldev->vcam_devices[dev_spec->idx]; 111 | 112 | spin_lock_irqsave(&dev->in_fh_slock, flags); 113 | if (dev->fb_isopen || vb2_is_busy(&dev->vb_out_vidq)) { 114 | spin_unlock_irqrestore(&dev->in_fh_slock, flags); 115 | return -EBUSY; 116 | } 117 | dev->fb_isopen = true; 118 | spin_unlock_irqrestore(&dev->in_fh_slock, flags); 119 | 120 | spin_lock_irqsave(&ctldev->vcam_devices_lock, flags); 121 | for (i = dev_spec->idx; i < (ctldev->vcam_device_count); i++) 122 | ctldev->vcam_devices[i] = ctldev->vcam_devices[i + 1]; 123 | ctldev->vcam_devices[--ctldev->vcam_device_count] = NULL; 124 | spin_unlock_irqrestore(&ctldev->vcam_devices_lock, flags); 125 | 126 | destroy_vcam_device(dev); 127 | 128 | return 0; 129 | } 130 | 131 | static long control_ioctl(struct file *file, 132 | unsigned int iocontrol_cmd, 133 | unsigned long iocontrol_param) 134 | { 135 | struct vcam_device_spec dev_spec; 136 | long ret = copy_from_user(&dev_spec, (void __user *) iocontrol_param, 137 | sizeof(struct vcam_device_spec)); 138 | if (ret != 0) { 139 | pr_warn("Failed to copy_from_user!"); 140 | return -1; 141 | } 142 | switch (iocontrol_cmd) { 143 | case VCAM_IOCTL_CREATE_DEVICE: 144 | pr_debug("Requesing new device\n"); 145 | ret = request_vcam_device(&dev_spec); 146 | break; 147 | case VCAM_IOCTL_DESTROY_DEVICE: 148 | pr_debug("Requesting removal of device\n"); 149 | ret = control_iocontrol_destroy_device(&dev_spec); 150 | break; 151 | case VCAM_IOCTL_GET_DEVICE: 152 | pr_debug("Get device(%d)\n", dev_spec.idx); 153 | ret = control_iocontrol_get_device(&dev_spec); 154 | if (!ret) { 155 | if (copy_to_user((void *__user *) iocontrol_param, &dev_spec, 156 | sizeof(struct vcam_device_spec)) != 0) { 157 | pr_warn("Failed to copy_to_user!"); 158 | ret = -1; 159 | } 160 | } 161 | break; 162 | case VCAM_IOCTL_MODIFY_SETTING: 163 | pr_debug("Modify setting(%d)\n", dev_spec.idx); 164 | ret = control_iocontrol_modify_input_setting(&dev_spec); 165 | break; 166 | default: 167 | ret = -1; 168 | } 169 | return ret; 170 | } 171 | 172 | static struct vcam_device_spec default_vcam_spec = { 173 | .width = 640, 174 | .height = 480, 175 | .cropratio = {.numerator = 3, .denominator = 4}, 176 | .pix_fmt = VCAM_PIXFMT_RGB24, 177 | }; 178 | 179 | int request_vcam_device(struct vcam_device_spec *dev_spec) 180 | { 181 | struct vcam_device *vcam; 182 | int idx; 183 | unsigned long flags = 0; 184 | 185 | if (!ctldev) 186 | return -ENODEV; 187 | 188 | if (ctldev->vcam_device_count > devices_max) 189 | return -ENOMEM; 190 | 191 | if (!dev_spec) 192 | vcam = 193 | create_vcam_device(ctldev->vcam_device_count, &default_vcam_spec); 194 | else 195 | vcam = create_vcam_device(ctldev->vcam_device_count, dev_spec); 196 | 197 | if (!vcam) 198 | return -ENODEV; 199 | 200 | spin_lock_irqsave(&ctldev->vcam_devices_lock, flags); 201 | idx = ctldev->vcam_device_count++; 202 | ctldev->vcam_devices[idx] = vcam; 203 | spin_unlock_irqrestore(&ctldev->vcam_devices_lock, flags); 204 | return 0; 205 | } 206 | 207 | static struct control_device *alloc_control_device(void) 208 | { 209 | struct control_device *res = 210 | (struct control_device *) kmalloc(sizeof(*res), GFP_KERNEL); 211 | if (!res) 212 | goto return_res; 213 | 214 | res->vcam_devices = (struct vcam_device **) kmalloc( 215 | sizeof(struct vcam_device *) * devices_max, GFP_KERNEL); 216 | if (!(res->vcam_devices)) 217 | goto vcam_alloc_failure; 218 | memset(res->vcam_devices, 0x00, 219 | sizeof(struct vcam_devices *) * devices_max); 220 | res->vcam_device_count = 0; 221 | 222 | return res; 223 | 224 | vcam_alloc_failure: 225 | kfree(res); 226 | res = NULL; 227 | return_res: 228 | return res; 229 | } 230 | 231 | static void free_control_device(struct control_device *dev) 232 | { 233 | size_t i; 234 | for (i = 0; i < dev->vcam_device_count; i++) 235 | destroy_vcam_device(dev->vcam_devices[i]); 236 | kfree(dev->vcam_devices); 237 | device_destroy(dev->dev_class, dev->dev_number); 238 | class_destroy(dev->dev_class); 239 | cdev_del(&dev->cdev); 240 | unregister_chrdev_region(dev->dev_number, 1); 241 | kfree(dev); 242 | } 243 | 244 | static struct file_operations control_fops = { 245 | .owner = THIS_MODULE, 246 | .read = control_read, 247 | .write = control_write, 248 | .open = control_open, 249 | .release = control_release, 250 | .unlocked_ioctl = control_ioctl, 251 | }; 252 | 253 | int __init create_control_device(const char *dev_name) 254 | { 255 | int ret = 0; 256 | 257 | ctldev = alloc_control_device(); 258 | if (!ctldev) { 259 | pr_err("kmalloc_failed\n"); 260 | ret = -ENOMEM; 261 | goto kmalloc_failure; 262 | } 263 | 264 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) 265 | ctldev->dev_class = class_create(dev_name); 266 | #else 267 | ctldev->dev_class = class_create(THIS_MODULE, dev_name); 268 | #endif 269 | if (!(ctldev->dev_class)) { 270 | pr_err("Error creating device class for control device\n"); 271 | ret = -ENODEV; 272 | goto class_create_failure; 273 | } 274 | 275 | cdev_init(&ctldev->cdev, &control_fops); 276 | ctldev->cdev.owner = THIS_MODULE; 277 | 278 | ret = alloc_chrdev_region(&ctldev->dev_number, 0, 1, dev_name); 279 | if (ret) { 280 | pr_err("Error allocating device number\n"); 281 | goto alloc_chrdev_error; 282 | } 283 | 284 | ret = cdev_add(&ctldev->cdev, ctldev->dev_number, 1); 285 | pr_debug("cdev_add returned %d", ret); 286 | if (ret < 0) { 287 | pr_err("device registration failure\n"); 288 | goto registration_failure; 289 | } 290 | 291 | ctldev->device = device_create(ctldev->dev_class, NULL, ctldev->dev_number, 292 | NULL, dev_name, MINOR(ctldev->dev_number)); 293 | if (!ctldev->device) { 294 | pr_err("device_create failed\n"); 295 | ret = -ENODEV; 296 | goto device_create_failure; 297 | } 298 | 299 | spin_lock_init(&ctldev->vcam_devices_lock); 300 | 301 | return 0; 302 | device_create_failure: 303 | cdev_del(&ctldev->cdev); 304 | registration_failure: 305 | unregister_chrdev_region(ctldev->dev_number, 1); 306 | class_destroy(ctldev->dev_class); 307 | alloc_chrdev_error: 308 | class_create_failure: 309 | free_control_device(ctldev); 310 | ctldev = NULL; 311 | kmalloc_failure: 312 | return ret; 313 | } 314 | 315 | void __exit destroy_control_device(void) 316 | { 317 | if (ctldev) { 318 | free_control_device(ctldev); 319 | ctldev = NULL; 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /control.h: -------------------------------------------------------------------------------- 1 | #ifndef VCAM_CONTROL_H 2 | #define VCAM_CONTROL_H 3 | 4 | #include "vcam.h" 5 | 6 | int create_control_device(const char *dev_name); 7 | void destroy_control_device(void); 8 | 9 | /* request new virtual camera device */ 10 | int request_vcam_device(struct vcam_device_spec *dev_spec); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /device.c: -------------------------------------------------------------------------------- 1 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "device.h" 11 | #include "fb.h" 12 | #include "videobuf.h" 13 | 14 | extern const char *vcam_dev_name; 15 | extern unsigned char allow_pix_conversion; 16 | extern unsigned char allow_scaling; 17 | extern unsigned char allow_cropping; 18 | 19 | struct __attribute__((__packed__)) rgb_struct { 20 | unsigned char r, g, b; 21 | }; 22 | 23 | static const struct vcam_device_format vcam_supported_fmts[] = { 24 | { 25 | .name = "RGB24 (LE)", 26 | .fourcc = V4L2_PIX_FMT_RGB24, 27 | .bit_depth = 24, 28 | }, 29 | { 30 | .name = "YUV 4:2:2 (YUYV)", 31 | .fourcc = V4L2_PIX_FMT_YUYV, 32 | .bit_depth = 16, 33 | }, 34 | }; 35 | 36 | static const struct v4l2_file_operations vcam_fops = { 37 | .owner = THIS_MODULE, 38 | .open = v4l2_fh_open, 39 | .release = vb2_fop_release, 40 | .read = vb2_fop_read, 41 | .poll = vb2_fop_poll, 42 | .unlocked_ioctl = video_ioctl2, 43 | .mmap = vb2_fop_mmap, 44 | }; 45 | 46 | static const struct v4l2_frmsize_discrete vcam_sizes[] = { 47 | {480, 360}, 48 | {VGA_WIDTH, VGA_HEIGHT}, 49 | {HD_720_WIDTH, HD_720_HEIGHT}, 50 | }; 51 | 52 | static int vcam_querycap(struct file *file, 53 | void *priv, 54 | struct v4l2_capability *cap) 55 | { 56 | strcpy(cap->driver, vcam_dev_name); 57 | strcpy(cap->card, vcam_dev_name); 58 | strcpy(cap->bus_info, "platform: virtual"); 59 | cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | 60 | V4L2_CAP_READWRITE | V4L2_CAP_DEVICE_CAPS; 61 | 62 | return 0; 63 | } 64 | 65 | static int vcam_enum_input(struct file *file, 66 | void *priv, 67 | struct v4l2_input *inp) 68 | { 69 | if (inp->index >= 1) 70 | return -EINVAL; 71 | 72 | inp->type = V4L2_INPUT_TYPE_CAMERA; 73 | inp->capabilities = 0; 74 | sprintf(inp->name, "vcam_in %u", inp->index); 75 | return 0; 76 | } 77 | 78 | static int vcam_g_input(struct file *file, void *priv, unsigned int *i) 79 | { 80 | *i = 0; 81 | return 0; 82 | } 83 | 84 | static int vcam_s_input(struct file *file, void *priv, unsigned int i) 85 | { 86 | return (i >= 1) ? -EINVAL : 0; 87 | } 88 | 89 | static int vcam_enum_fmt_vid_cap(struct file *file, 90 | void *priv, 91 | struct v4l2_fmtdesc *f) 92 | { 93 | struct vcam_device_format *fmt; 94 | struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 95 | int idx = f->index; 96 | 97 | if (idx >= dev->nr_fmts) 98 | return -EINVAL; 99 | 100 | fmt = &dev->out_fmts[idx]; 101 | strcpy(f->description, fmt->name); 102 | f->pixelformat = fmt->fourcc; 103 | return 0; 104 | } 105 | 106 | static int vcam_g_fmt_vid_cap(struct file *file, 107 | void *priv, 108 | struct v4l2_format *f) 109 | { 110 | struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 111 | memcpy(&f->fmt.pix, &dev->output_format, sizeof(struct v4l2_pix_format)); 112 | return 0; 113 | } 114 | 115 | static bool check_supported_pixfmt(struct vcam_device *dev, unsigned int fourcc) 116 | { 117 | int i; 118 | for (i = 0; i < dev->nr_fmts; i++) { 119 | if (dev->out_fmts[i].fourcc == fourcc) 120 | break; 121 | } 122 | 123 | return (i == dev->nr_fmts) ? false : true; 124 | } 125 | 126 | static void negotiate_resolution(__u32 *width, __u32 *height) 127 | { 128 | int n_avail = ARRAY_SIZE(vcam_sizes); 129 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) 130 | const struct v4l2_frmsize_discrete *sz = v4l2_find_nearest_size( 131 | vcam_sizes, n_avail, width, height, *width, *height); 132 | #else 133 | const struct v4l2_discrete_probe vcam_probe = {vcam_sizes, n_avail}; 134 | const struct v4l2_frmsize_discrete *sz = 135 | v4l2_find_nearest_format(&vcam_probe, *width, *height); 136 | #endif 137 | *width = sz->width; 138 | *height = sz->height; 139 | } 140 | 141 | static int vcam_try_fmt_vid_cap(struct file *file, 142 | void *priv, 143 | struct v4l2_format *f) 144 | { 145 | struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 146 | 147 | if (!check_supported_pixfmt(dev, f->fmt.pix.pixelformat)) { 148 | f->fmt.pix.pixelformat = dev->output_format.pixelformat; 149 | pr_debug("Unsupported\n"); 150 | } 151 | 152 | if (!dev->conv_res_on) { 153 | pr_debug("Resolution conversion is %d\n", dev->conv_res_on); 154 | f->fmt.pix.width = dev->output_format.width; 155 | f->fmt.pix.height = dev->output_format.height; 156 | } else if (!dev->conv_crop_on) { 157 | negotiate_resolution(&f->fmt.pix.width, &f->fmt.pix.height); 158 | } else { 159 | /* set the cropping rectangular resolution */ 160 | struct crop_ratio cropratio = dev->fb_spec.cropratio; 161 | f->fmt.pix.width = 162 | f->fmt.pix.width / cropratio.numerator * cropratio.denominator; 163 | f->fmt.pix.height = 164 | f->fmt.pix.height / cropratio.numerator * cropratio.denominator; 165 | negotiate_resolution(&f->fmt.pix.width, &f->fmt.pix.height); 166 | set_crop_resolution(&f->fmt.pix.width, &f->fmt.pix.height, cropratio); 167 | } 168 | 169 | f->fmt.pix.field = V4L2_FIELD_NONE; 170 | if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { 171 | f->fmt.pix.bytesperline = f->fmt.pix.width << 1; 172 | f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; 173 | } else { 174 | f->fmt.pix.bytesperline = f->fmt.pix.width * 3; 175 | f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; 176 | } 177 | f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; 178 | 179 | return 0; 180 | } 181 | 182 | static int vcam_s_fmt_vid_cap(struct file *file, 183 | void *priv, 184 | struct v4l2_format *f) 185 | { 186 | int ret; 187 | 188 | struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 189 | 190 | ret = vcam_try_fmt_vid_cap(file, priv, f); 191 | if (ret < 0) 192 | return ret; 193 | 194 | dev->output_format = f->fmt.pix; 195 | 196 | pr_debug("Resolution set to %dx%d\n", dev->output_format.width, 197 | dev->output_format.height); 198 | return 0; 199 | } 200 | 201 | static int vcam_enum_frameintervals(struct file *file, 202 | void *priv, 203 | struct v4l2_frmivalenum *fival) 204 | { 205 | struct v4l2_frmival_stepwise *frm_step; 206 | struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 207 | 208 | if (fival->index > 0) { 209 | pr_debug("Index out of range\n"); 210 | return -EINVAL; 211 | } 212 | 213 | if (!check_supported_pixfmt(dev, fival->pixel_format)) { 214 | pr_debug("Unsupported pixfmt\n"); 215 | return -EINVAL; 216 | } 217 | 218 | if (!dev->conv_res_on) { 219 | if ((fival->width != dev->output_format.width) || 220 | (fival->height != dev->output_format.height)) { 221 | pr_debug("Unsupported resolution\n"); 222 | return -EINVAL; 223 | } 224 | } 225 | 226 | if ((fival->width % 2) || (fival->height % 2)) { 227 | pr_debug("Unsupported resolution\n"); 228 | return -EINVAL; 229 | } 230 | 231 | fival->type = V4L2_FRMIVAL_TYPE_STEPWISE; 232 | frm_step = &fival->stepwise; 233 | frm_step->min.numerator = 1001; 234 | frm_step->min.denominator = 60000; 235 | frm_step->max.numerator = 1001; 236 | frm_step->max.denominator = 1001; 237 | frm_step->step.numerator = 1001; 238 | frm_step->step.denominator = 60000; 239 | 240 | return 0; 241 | } 242 | 243 | static int vcam_g_parm(struct file *file, 244 | void *priv, 245 | struct v4l2_streamparm *sp) 246 | { 247 | struct vcam_device *dev; 248 | struct v4l2_captureparm *cp; 249 | 250 | if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) 251 | return -EINVAL; 252 | 253 | cp = &sp->parm.capture; 254 | dev = (struct vcam_device *) video_drvdata(file); 255 | 256 | memset(cp, 0x00, sizeof(struct v4l2_captureparm)); 257 | cp->capability = V4L2_CAP_TIMEPERFRAME; 258 | cp->timeperframe = dev->output_fps; 259 | cp->extendedmode = 0; 260 | cp->readbuffers = 1; 261 | 262 | return 0; 263 | } 264 | 265 | static int vcam_s_parm(struct file *file, 266 | void *priv, 267 | struct v4l2_streamparm *sp) 268 | { 269 | struct vcam_device *dev; 270 | struct v4l2_captureparm *cp; 271 | 272 | if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) 273 | return -EINVAL; 274 | 275 | cp = &sp->parm.capture; 276 | dev = (struct vcam_device *) video_drvdata(file); 277 | 278 | cp->capability = V4L2_CAP_TIMEPERFRAME; 279 | if (!cp->timeperframe.numerator || !cp->timeperframe.denominator) 280 | cp->timeperframe = dev->output_fps; 281 | else 282 | dev->output_fps = cp->timeperframe; 283 | cp->extendedmode = 0; 284 | cp->readbuffers = 1; 285 | 286 | pr_debug("FPS set to %d/%d\n", cp->timeperframe.numerator, 287 | cp->timeperframe.denominator); 288 | return 0; 289 | } 290 | 291 | static int vcam_enum_framesizes(struct file *file, 292 | void *priv, 293 | struct v4l2_frmsizeenum *fsize) 294 | { 295 | struct v4l2_frmsize_discrete *size_discrete; 296 | 297 | struct vcam_device *dev = (struct vcam_device *) video_drvdata(file); 298 | if (!check_supported_pixfmt(dev, fsize->pixel_format)) 299 | return -EINVAL; 300 | 301 | if (!dev->conv_res_on) { 302 | if (fsize->index > 0) 303 | return -EINVAL; 304 | 305 | fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; 306 | size_discrete = &fsize->discrete; 307 | size_discrete->width = dev->output_format.width; 308 | size_discrete->height = dev->output_format.height; 309 | } else if (dev->conv_res_on) { 310 | if (fsize->index > 0) 311 | return -EINVAL; 312 | 313 | fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; 314 | size_discrete = &fsize->discrete; 315 | size_discrete->width = dev->output_format.width; 316 | size_discrete->height = dev->output_format.height; 317 | } else { 318 | if (fsize->index > 0) 319 | return -EINVAL; 320 | 321 | fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; 322 | fsize->stepwise.min_width = 64; 323 | fsize->stepwise.max_width = 1280; 324 | fsize->stepwise.step_width = 2; 325 | fsize->stepwise.min_height = 64; 326 | fsize->stepwise.max_height = 720; 327 | fsize->stepwise.step_height = 2; 328 | } 329 | 330 | return 0; 331 | } 332 | 333 | static const struct v4l2_ioctl_ops vcam_ioctl_ops = { 334 | .vidioc_querycap = vcam_querycap, 335 | .vidioc_enum_input = vcam_enum_input, 336 | .vidioc_g_input = vcam_g_input, 337 | .vidioc_s_input = vcam_s_input, 338 | .vidioc_enum_fmt_vid_cap = vcam_enum_fmt_vid_cap, 339 | .vidioc_g_fmt_vid_cap = vcam_g_fmt_vid_cap, 340 | .vidioc_try_fmt_vid_cap = vcam_try_fmt_vid_cap, 341 | .vidioc_s_fmt_vid_cap = vcam_s_fmt_vid_cap, 342 | .vidioc_g_parm = vcam_g_parm, 343 | .vidioc_s_parm = vcam_s_parm, 344 | .vidioc_enum_frameintervals = vcam_enum_frameintervals, 345 | .vidioc_enum_framesizes = vcam_enum_framesizes, 346 | .vidioc_reqbufs = vb2_ioctl_reqbufs, 347 | .vidioc_create_bufs = vb2_ioctl_create_bufs, 348 | .vidioc_prepare_buf = vb2_ioctl_prepare_buf, 349 | .vidioc_querybuf = vb2_ioctl_querybuf, 350 | .vidioc_qbuf = vb2_ioctl_qbuf, 351 | .vidioc_dqbuf = vb2_ioctl_dqbuf, 352 | .vidioc_expbuf = vb2_ioctl_expbuf, 353 | .vidioc_streamon = vb2_ioctl_streamon, 354 | .vidioc_streamoff = vb2_ioctl_streamoff}; 355 | 356 | static const struct video_device vcam_video_device_template = { 357 | .fops = &vcam_fops, 358 | .ioctl_ops = &vcam_ioctl_ops, 359 | .release = video_device_release_empty, 360 | }; 361 | 362 | static inline void rgb24_to_yuyv(void *dst, void *src) 363 | { 364 | unsigned char *rgb = (unsigned char *) src; 365 | unsigned char *yuyv = (unsigned char *) dst; 366 | yuyv[0] = ((66 * rgb[0] + 129 * rgb[1] + 25 * rgb[2]) >> 8) + 16; 367 | yuyv[1] = ((-38 * rgb[0] - 74 * rgb[1] + 112 * rgb[2]) >> 8) + 128; 368 | yuyv[2] = ((66 * rgb[3] + 129 * rgb[4] + 25 * rgb[5]) >> 8) + 16; 369 | yuyv[3] = ((112 * rgb[0] - 94 * rgb[1] - 18 * rgb[2]) >> 8) + 128; 370 | yuyv[0] = yuyv[0] > 240 ? 240 : yuyv[0]; 371 | yuyv[0] = yuyv[0] < 16 ? 16 : yuyv[0]; 372 | yuyv[1] = yuyv[1] > 235 ? 235 : yuyv[1]; 373 | yuyv[1] = yuyv[1] < 16 ? 16 : yuyv[1]; 374 | yuyv[2] = yuyv[2] > 240 ? 240 : yuyv[2]; 375 | yuyv[2] = yuyv[2] < 16 ? 16 : yuyv[2]; 376 | yuyv[3] = yuyv[3] > 235 ? 235 : yuyv[3]; 377 | yuyv[3] = yuyv[3] < 16 ? 16 : yuyv[3]; 378 | } 379 | 380 | static inline void yuyv_to_rgb24(void *dst, void *src) 381 | { 382 | unsigned char *rgb = (unsigned char *) dst; 383 | unsigned char *yuyv = (unsigned char *) src; 384 | int16_t r, g, b; 385 | int16_t c2 = yuyv[0] - 16; 386 | int16_t d = yuyv[1] - 128; 387 | int16_t c1 = yuyv[2] - 16; 388 | int16_t e = yuyv[3] - 128; 389 | 390 | r = (298 * c1 + 409 * e) >> 8; 391 | g = (298 * c1 - 100 * d - 208 * e) >> 8; 392 | b = (298 * c1 + 516 * d) >> 8; 393 | r = r > 255 ? 255 : r; 394 | r = r < 0 ? 0 : r; 395 | g = g > 255 ? 255 : g; 396 | g = g < 0 ? 0 : g; 397 | b = b > 255 ? 255 : b; 398 | b = b < 0 ? 0 : b; 399 | rgb[0] = (unsigned char) r; 400 | rgb[1] = (unsigned char) g; 401 | rgb[2] = (unsigned char) b; 402 | 403 | r = (298 * c2 + 409 * e) >> 8; 404 | g = (298 * c2 - 100 * d - 208 * e) >> 8; 405 | b = (298 * c2 + 516 * d) >> 8; 406 | r = r > 255 ? 255 : r; 407 | r = r < 0 ? 0 : r; 408 | g = g > 255 ? 255 : g; 409 | g = g < 0 ? 0 : g; 410 | b = b > 255 ? 255 : b; 411 | b = b < 0 ? 0 : b; 412 | rgb[3] = (unsigned char) r; 413 | rgb[4] = (unsigned char) g; 414 | rgb[5] = (unsigned char) b; 415 | } 416 | 417 | static inline void yuyv_to_rgb24_one_pix(void *dst, 418 | void *src, 419 | unsigned char even) 420 | { 421 | unsigned char *rgb = (unsigned char *) dst; 422 | unsigned char *yuyv = (unsigned char *) src; 423 | int16_t r, g, b; 424 | int16_t c = even ? yuyv[0] - 16 : yuyv[2] - 16; 425 | int16_t d = yuyv[1] - 128; 426 | int16_t e = yuyv[3] - 128; 427 | 428 | r = (298 * c + 409 * e + 128) >> 8; 429 | g = (298 * c - 100 * d - 208 * e + 128) >> 8; 430 | b = (298 * c + 516 * d + 128) >> 8; 431 | r = r > 255 ? 255 : r; 432 | r = r < 0 ? 0 : r; 433 | g = g > 255 ? 255 : g; 434 | g = g < 0 ? 0 : g; 435 | b = b > 255 ? 255 : b; 436 | b = b < 0 ? 0 : b; 437 | rgb[0] = (unsigned char) r; 438 | rgb[1] = (unsigned char) g; 439 | rgb[2] = (unsigned char) b; 440 | } 441 | 442 | static void submit_noinput_buffer(struct vcam_out_buffer *buf, 443 | struct vcam_device *dev) 444 | { 445 | int i, j; 446 | int32_t yuyv_tmp; 447 | unsigned char *yuyv_helper = (unsigned char *) &yuyv_tmp; 448 | void *vbuf_ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); 449 | int32_t *yuyv_ptr = vbuf_ptr; 450 | size_t size = dev->output_format.sizeimage; 451 | size_t rowsize = dev->output_format.bytesperline; 452 | size_t rows = dev->output_format.height; 453 | 454 | int stripe_size = (rows / 255); 455 | if (dev->output_format.pixelformat == V4L2_PIX_FMT_YUYV) { 456 | yuyv_tmp = 0x80808080; 457 | 458 | for (i = 0; i < 255; i++) { 459 | yuyv_helper[0] = (unsigned char) i; 460 | yuyv_helper[2] = (unsigned char) i; 461 | for (j = 0; j < ((rowsize * stripe_size) >> 2); j++) { 462 | *yuyv_ptr = yuyv_tmp; 463 | yuyv_ptr++; 464 | } 465 | } 466 | 467 | yuyv_tmp = 0x80ff80ff; 468 | while ((void *) yuyv_ptr < (void *) ((void *) vbuf_ptr + size)) { 469 | *yuyv_ptr = yuyv_tmp; 470 | yuyv_ptr++; 471 | } 472 | } else { 473 | for (i = 0; i < 255; i++) { 474 | memset(vbuf_ptr, i, rowsize * stripe_size); 475 | vbuf_ptr += rowsize * stripe_size; 476 | } 477 | 478 | if (rows % 255) 479 | memset(vbuf_ptr, 0xff, rowsize * (rows % 255)); 480 | } 481 | 482 | buf->vb.vb2_buf.timestamp = ktime_get_ns(); 483 | vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); 484 | } 485 | 486 | static void copy_scale(unsigned char *dst, 487 | unsigned char *src, 488 | struct vcam_device *dev) 489 | { 490 | uint32_t dst_height = dev->output_format.height; 491 | uint32_t dst_width = dev->output_format.width; 492 | uint32_t src_height = dev->input_format.height; 493 | uint32_t src_width = dev->input_format.width; 494 | uint32_t ratio_height = ((src_height << 16) / dst_height) + 1; 495 | int i, j; 496 | 497 | if (dev->output_format.pixelformat == V4L2_PIX_FMT_YUYV) { 498 | uint32_t *yuyv_dst = (uint32_t *) dst; 499 | uint32_t *yuyv_src = (uint32_t *) src; 500 | uint32_t ratio_width; 501 | dst_width >>= 1; 502 | src_width >>= 1; 503 | ratio_width = ((src_width << 16) / dst_width) + 1; 504 | for (i = 0; i < dst_height; i++) { 505 | int tmp1 = ((i * ratio_height) >> 16); 506 | for (j = 0; j < dst_width; j++) { 507 | int tmp2 = ((j * ratio_width) >> 16); 508 | yuyv_dst[(i * dst_width) + j] = 509 | yuyv_src[(tmp1 * src_width) + tmp2]; 510 | } 511 | } 512 | 513 | } else if (dev->output_format.pixelformat == V4L2_PIX_FMT_RGB24) { 514 | struct rgb_struct *yuyv_dst = (struct rgb_struct *) dst; 515 | struct rgb_struct *yuyv_src = (struct rgb_struct *) src; 516 | uint32_t ratio_width = ((src_width << 16) / dst_width) + 1; 517 | for (i = 0; i < dst_height; i++) { 518 | int tmp1 = ((i * ratio_height) >> 16); 519 | for (j = 0; j < dst_width; j++) { 520 | int tmp2 = ((j * ratio_width) >> 16); 521 | yuyv_dst[(i * dst_width) + j] = 522 | yuyv_src[(tmp1 * src_width) + tmp2]; 523 | } 524 | } 525 | } 526 | } 527 | 528 | static void copy_scale_rgb24_to_yuyv(unsigned char *dst, 529 | unsigned char *src, 530 | struct vcam_device *dev) 531 | { 532 | uint32_t dst_height = dev->output_format.height; 533 | uint32_t dst_width = dev->output_format.width; 534 | uint32_t src_height = dev->input_format.height; 535 | uint32_t src_width = dev->input_format.width; 536 | uint32_t ratio_height = ((src_height << 16) / dst_height) + 1; 537 | uint32_t ratio_width; 538 | int i, j; 539 | 540 | uint32_t *yuyv_dst = (uint32_t *) dst; 541 | struct rgb_struct *rgb_src = (struct rgb_struct *) src; 542 | dst_width >>= 1; 543 | src_width >>= 1; 544 | ratio_width = ((src_width << 16) / dst_width) + 1; 545 | for (i = 0; i < dst_height; i++) { 546 | int tmp1 = ((i * ratio_height) >> 16); 547 | for (j = 0; j < dst_width; j++) { 548 | int tmp2 = ((j * ratio_width) >> 16); 549 | rgb24_to_yuyv(yuyv_dst, 550 | &rgb_src[tmp1 * (src_width << 1) + (tmp2 << 1)]); 551 | yuyv_dst++; 552 | } 553 | } 554 | } 555 | 556 | static void copy_scale_yuyv_to_rgb24(unsigned char *dst, 557 | unsigned char *src, 558 | struct vcam_device *dev) 559 | { 560 | uint32_t dst_height = dev->output_format.height; 561 | uint32_t dst_width = dev->output_format.width; 562 | uint32_t src_height = dev->input_format.height; 563 | uint32_t src_width = dev->input_format.width; 564 | uint32_t ratio_height = ((src_height << 16) / dst_height) + 1; 565 | uint32_t ratio_width = ((src_width << 16) / dst_width) + 1; 566 | int i, j; 567 | 568 | struct rgb_struct *rgb_dst = (struct rgb_struct *) dst; 569 | int32_t *yuyv_src = (int32_t *) src; 570 | for (i = 0; i < dst_height; i++) { 571 | int tmp1 = ((i * ratio_height) >> 16); 572 | for (j = 0; j < dst_width; j++) { 573 | int tmp2 = ((j * ratio_width) >> 16); 574 | yuyv_to_rgb24_one_pix( 575 | rgb_dst, &yuyv_src[tmp1 * (src_width >> 1) + (tmp2 >> 1)], 576 | tmp2 & 0x01); 577 | rgb_dst++; 578 | } 579 | } 580 | } 581 | 582 | static void convert_rgb24_buf_to_yuyv(unsigned char *dst, 583 | unsigned char *src, 584 | size_t pixel_count) 585 | { 586 | int i; 587 | pixel_count >>= 1; 588 | for (i = 0; i < pixel_count; i++) { 589 | rgb24_to_yuyv(dst, src); 590 | dst += 4; 591 | src += 6; 592 | } 593 | } 594 | 595 | static void convert_yuyv_buf_to_rgb24(unsigned char *dst, 596 | unsigned char *src, 597 | size_t pixel_count) 598 | { 599 | int i; 600 | pixel_count >>= 1; 601 | for (i = 0; i < pixel_count; i++) { 602 | yuyv_to_rgb24(dst, src); 603 | dst += 6; 604 | src += 4; 605 | } 606 | } 607 | 608 | static void submit_copy_buffer(struct vcam_out_buffer *out_buf, 609 | struct vcam_in_buffer *in_buf, 610 | struct vcam_device *dev) 611 | { 612 | void *in_vbuf_ptr, *out_vbuf_ptr; 613 | 614 | in_vbuf_ptr = in_buf->data; 615 | if (!in_vbuf_ptr) { 616 | pr_err("Input buffer is NULL in ready state\n"); 617 | return; 618 | } 619 | out_vbuf_ptr = vb2_plane_vaddr(&out_buf->vb.vb2_buf, 0); 620 | if (!out_vbuf_ptr) { 621 | pr_err("Output buffer is NULL\n"); 622 | return; 623 | } 624 | 625 | if (dev->output_format.pixelformat == dev->input_format.pixelformat) { 626 | pr_debug("Same pixel format\n"); 627 | pr_debug("%d,%d -> %d,%d\n", dev->output_format.width, 628 | dev->output_format.height, dev->input_format.width, 629 | dev->input_format.height); 630 | if (dev->output_format.width == dev->input_format.width && 631 | dev->output_format.height == dev->input_format.height) { 632 | pr_debug("No scaling\n"); 633 | memcpy(out_vbuf_ptr, in_vbuf_ptr, in_buf->filled); 634 | } else { 635 | pr_debug("Scaling\n"); 636 | copy_scale(out_vbuf_ptr, in_vbuf_ptr, dev); 637 | } 638 | } else { 639 | if (dev->output_format.width == dev->input_format.width && 640 | dev->output_format.height == dev->input_format.height) { 641 | int pixel_count = 642 | dev->input_format.height * dev->input_format.width; 643 | if (dev->input_format.pixelformat == V4L2_PIX_FMT_YUYV) { 644 | pr_debug("YUYV->RGB24 no scale\n"); 645 | convert_yuyv_buf_to_rgb24(out_vbuf_ptr, in_vbuf_ptr, 646 | pixel_count); 647 | } else { 648 | pr_debug("RGB24->YUYV no scale\n"); 649 | convert_rgb24_buf_to_yuyv(out_vbuf_ptr, in_vbuf_ptr, 650 | pixel_count); 651 | } 652 | } else { 653 | if (dev->output_format.pixelformat == V4L2_PIX_FMT_YUYV) { 654 | pr_debug("RGB24->YUYV scale\n"); 655 | copy_scale_rgb24_to_yuyv(out_vbuf_ptr, in_vbuf_ptr, dev); 656 | } else if (dev->output_format.pixelformat == V4L2_PIX_FMT_RGB24) { 657 | pr_debug("RGB24->YUYV scale\n"); 658 | copy_scale_yuyv_to_rgb24(out_vbuf_ptr, in_vbuf_ptr, dev); 659 | } 660 | } 661 | } 662 | out_buf->vb.vb2_buf.timestamp = ktime_get_ns(); 663 | vb2_buffer_done(&out_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); 664 | } 665 | 666 | int submitter_thread(void *data) 667 | { 668 | unsigned long flags = 0; 669 | struct vcam_device *dev = (struct vcam_device *) data; 670 | struct vcam_out_queue *q = &dev->vcam_out_vidq; 671 | struct vcam_in_queue *in_q = &dev->in_queue; 672 | 673 | while (!kthread_should_stop()) { 674 | struct vcam_out_buffer *buf; 675 | int timeout_ms, timeout; 676 | 677 | /* Do something and sleep */ 678 | int computation_time_jiff = jiffies; 679 | spin_lock_irqsave(&dev->out_q_slock, flags); 680 | if (list_empty(&q->active)) { 681 | pr_debug("Buffer queue is empty\n"); 682 | spin_unlock_irqrestore(&dev->out_q_slock, flags); 683 | goto have_a_nap; 684 | } 685 | buf = list_entry(q->active.next, struct vcam_out_buffer, list); 686 | list_del(&buf->list); 687 | spin_unlock_irqrestore(&dev->out_q_slock, flags); 688 | 689 | if (!dev->fb_isopen) { 690 | submit_noinput_buffer(buf, dev); 691 | } else { 692 | struct vcam_in_buffer *in_buf; 693 | spin_lock_irqsave(&dev->in_q_slock, flags); 694 | in_buf = in_q->ready; 695 | if (!in_buf) { 696 | pr_err("Ready buffer in input queue has NULL pointer\n"); 697 | goto unlock_and_continue; 698 | } 699 | submit_copy_buffer(buf, in_buf, dev); 700 | unlock_and_continue: 701 | spin_unlock_irqrestore(&dev->in_q_slock, flags); 702 | } 703 | 704 | have_a_nap: 705 | if (!dev->output_fps.denominator) { 706 | dev->output_fps.numerator = 1001; 707 | dev->output_fps.denominator = 30000; 708 | } 709 | timeout_ms = dev->output_fps.denominator / dev->output_fps.numerator; 710 | if (!timeout_ms) { 711 | dev->output_fps.numerator = 1001; 712 | dev->output_fps.denominator = 60000; 713 | timeout_ms = 714 | dev->output_fps.denominator / dev->output_fps.numerator; 715 | } 716 | 717 | /* Compute timeout and update FPS */ 718 | computation_time_jiff = jiffies - computation_time_jiff; 719 | timeout = msecs_to_jiffies(timeout_ms); 720 | if (computation_time_jiff > timeout) { 721 | int computation_time_ms = msecs_to_jiffies(computation_time_jiff); 722 | dev->output_fps.numerator = 1001; 723 | dev->output_fps.denominator = 1000 * computation_time_ms; 724 | } else if (timeout > computation_time_jiff) { 725 | schedule_timeout_interruptible(timeout - computation_time_jiff); 726 | } 727 | } 728 | 729 | return 0; 730 | } 731 | 732 | static void fill_v4l2pixfmt(struct v4l2_pix_format *fmt, 733 | struct vcam_device_spec *dev_spec) 734 | { 735 | if (!fmt || !dev_spec) 736 | return; 737 | 738 | memset(fmt, 0x00, sizeof(struct v4l2_pix_format)); 739 | fmt->width = dev_spec->width; 740 | fmt->height = dev_spec->height; 741 | pr_debug("Filling %dx%d\n", dev_spec->width, dev_spec->height); 742 | 743 | switch (dev_spec->pix_fmt) { 744 | case VCAM_PIXFMT_RGB24: 745 | fmt->pixelformat = V4L2_PIX_FMT_RGB24; 746 | fmt->bytesperline = (fmt->width * 3); 747 | fmt->colorspace = V4L2_COLORSPACE_SRGB; 748 | break; 749 | case VCAM_PIXFMT_YUYV: 750 | fmt->pixelformat = V4L2_PIX_FMT_YUYV; 751 | fmt->bytesperline = (fmt->width) << 1; 752 | fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; 753 | break; 754 | default: 755 | fmt->pixelformat = V4L2_PIX_FMT_RGB24; 756 | fmt->bytesperline = (fmt->width * 3); 757 | fmt->colorspace = V4L2_COLORSPACE_SRGB; 758 | break; 759 | } 760 | 761 | fmt->field = V4L2_FIELD_NONE; 762 | fmt->sizeimage = fmt->height * fmt->bytesperline; 763 | } 764 | 765 | struct vcam_device *create_vcam_device(size_t idx, 766 | struct vcam_device_spec *dev_spec) 767 | { 768 | struct video_device *vdev; 769 | int i, ret = 0; 770 | 771 | struct vcam_device *vcam = 772 | (struct vcam_device *) kzalloc(sizeof(struct vcam_device), GFP_KERNEL); 773 | if (!vcam) 774 | goto vcam_alloc_failure; 775 | 776 | /* Register V4L2 device */ 777 | snprintf(vcam->v4l2_dev.name, sizeof(vcam->v4l2_dev.name), "%s-%d", 778 | vcam_dev_name, (int) idx); 779 | ret = v4l2_device_register(NULL, &vcam->v4l2_dev); 780 | if (ret) { 781 | pr_err("v4l2 registration failure\n"); 782 | goto v4l2_registration_failure; 783 | } 784 | 785 | /* Initialize buffer queue and device structures */ 786 | mutex_init(&vcam->vcam_mutex); 787 | 788 | /* Try to initialize output buffer */ 789 | ret = vcam_out_videobuf2_setup(vcam); 790 | if (ret) { 791 | pr_err(" failed to initialize output videobuffer\n"); 792 | goto vb2_out_init_failed; 793 | } 794 | 795 | spin_lock_init(&vcam->out_q_slock); 796 | spin_lock_init(&vcam->in_q_slock); 797 | spin_lock_init(&vcam->in_fh_slock); 798 | 799 | INIT_LIST_HEAD(&vcam->vcam_out_vidq.active); 800 | 801 | vdev = &vcam->vdev; 802 | *vdev = vcam_video_device_template; 803 | vdev->v4l2_dev = &vcam->v4l2_dev; 804 | vdev->queue = &vcam->vb_out_vidq; 805 | vdev->lock = &vcam->vcam_mutex; 806 | vdev->tvnorms = 0; 807 | vdev->device_caps = 808 | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; 809 | 810 | snprintf(vdev->name, sizeof(vdev->name), "%s-%d", vcam_dev_name, (int) idx); 811 | video_set_drvdata(vdev, vcam); 812 | 813 | ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); 814 | 815 | if (ret < 0) { 816 | pr_err("video_register_device failure\n"); 817 | goto video_regdev_failure; 818 | } 819 | 820 | /* Setup conversion capabilities */ 821 | vcam->conv_res_on = (bool) allow_scaling; 822 | vcam->conv_pixfmt_on = (bool) allow_pix_conversion; 823 | vcam->conv_crop_on = (bool) allow_cropping; 824 | 825 | /* Alloc and set initial format */ 826 | if (vcam->conv_pixfmt_on) { 827 | for (i = 0; i < ARRAY_SIZE(vcam_supported_fmts); i++) 828 | vcam->out_fmts[i] = vcam_supported_fmts[i]; 829 | vcam->nr_fmts = i; 830 | } else { 831 | if (dev_spec && dev_spec->pix_fmt == VCAM_PIXFMT_YUYV) 832 | vcam->out_fmts[0] = vcam_supported_fmts[1]; 833 | else 834 | vcam->out_fmts[0] = vcam_supported_fmts[0]; 835 | vcam->nr_fmts = 1; 836 | } 837 | 838 | if (vcam->conv_res_on) { 839 | /* Specify the highest resolution initially */ 840 | int n_avail = ARRAY_SIZE(vcam_sizes); 841 | dev_spec->width = vcam_sizes[n_avail - 1].width; 842 | dev_spec->height = vcam_sizes[n_avail - 1].height; 843 | } 844 | 845 | dev_spec->xres_virtual = dev_spec->width; 846 | dev_spec->yres_virtual = dev_spec->height; 847 | 848 | if (vcam->conv_crop_on) { 849 | set_crop_resolution(&dev_spec->width, &dev_spec->height, 850 | dev_spec->cropratio); 851 | } else { 852 | dev_spec->cropratio.numerator = 1; 853 | dev_spec->cropratio.denominator = 1; 854 | } 855 | vcam->fb_spec = *dev_spec; 856 | 857 | fill_v4l2pixfmt(&vcam->output_format, dev_spec); 858 | fill_v4l2pixfmt(&vcam->input_format, dev_spec); 859 | 860 | vcam->sub_thr_id = NULL; 861 | 862 | /* Initialize framebuffer */ 863 | ret = vcamfb_init(vcam); 864 | if (ret < 0) { 865 | pr_err("Failed to initialize vcamfb\n"); 866 | goto vcamfb_failure; 867 | } 868 | vcam->fb_isopen = 0; 869 | 870 | vcam->output_fps.numerator = 1001; 871 | vcam->output_fps.denominator = 30000; 872 | 873 | return vcam; 874 | 875 | vcamfb_failure: 876 | vcamfb_destroy(vcam); 877 | video_regdev_failure: 878 | video_unregister_device(&vcam->vdev); 879 | video_device_release(&vcam->vdev); 880 | vb2_out_init_failed: 881 | v4l2_device_unregister(&vcam->v4l2_dev); 882 | v4l2_registration_failure: 883 | kfree(vcam); 884 | vcam_alloc_failure: 885 | return NULL; 886 | } 887 | 888 | int modify_vcam_device(struct vcam_device *vcam, 889 | struct vcam_device_spec *dev_spec) 890 | { 891 | unsigned long flags = 0; 892 | 893 | spin_lock_irqsave(&vcam->in_fh_slock, flags); 894 | if (vcam->fb_isopen) { 895 | spin_unlock_irqrestore(&vcam->in_fh_slock, flags); 896 | return -EBUSY; 897 | } 898 | vcam->fb_isopen = true; 899 | spin_unlock_irqrestore(&vcam->in_fh_slock, flags); 900 | 901 | dev_spec->xres_virtual = dev_spec->width; 902 | dev_spec->yres_virtual = dev_spec->height; 903 | 904 | if (vcam->conv_crop_on) { 905 | set_crop_resolution(&dev_spec->width, &dev_spec->height, 906 | dev_spec->cropratio); 907 | } else { 908 | dev_spec->cropratio.numerator = 1; 909 | dev_spec->cropratio.denominator = 1; 910 | } 911 | vcam->fb_spec = *dev_spec; 912 | fill_v4l2pixfmt(&vcam->input_format, dev_spec); 913 | vcamfb_update(vcam); 914 | vcam->output_format = vcam->input_format; 915 | 916 | spin_lock_irqsave(&vcam->in_fh_slock, flags); 917 | vcam->fb_isopen = false; 918 | spin_unlock_irqrestore(&vcam->in_fh_slock, flags); 919 | 920 | pr_debug("Input format set (%dx%d)(%dx%d)\n", dev_spec->xres_virtual, 921 | dev_spec->yres_virtual, dev_spec->width, dev_spec->height); 922 | 923 | return 0; 924 | } 925 | 926 | void destroy_vcam_device(struct vcam_device *vcam) 927 | { 928 | if (!vcam) 929 | return; 930 | 931 | if (vcam->sub_thr_id) 932 | kthread_stop(vcam->sub_thr_id); 933 | vcamfb_destroy(vcam); 934 | mutex_destroy(&vcam->vcam_mutex); 935 | video_unregister_device(&vcam->vdev); 936 | v4l2_device_unregister(&vcam->v4l2_dev); 937 | 938 | kfree(vcam); 939 | } 940 | -------------------------------------------------------------------------------- /device.h: -------------------------------------------------------------------------------- 1 | #ifndef VCAM_DEVICE_H 2 | #define VCAM_DEVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "vcam.h" 13 | 14 | #define PIXFMTS_MAX 4 15 | #define FB_NAME_MAXLENGTH 16 16 | 17 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) 18 | #define VFL_TYPE_VIDEO VFL_TYPE_GRABBER 19 | #define HD_720_WIDTH 1280 20 | #define HD_720_HEIGHT 720 21 | #endif 22 | 23 | struct vcam_in_buffer { 24 | void *data; 25 | size_t filled; 26 | size_t xbar, ybar; 27 | uint32_t jiffies; 28 | }; 29 | 30 | struct vcam_in_queue { 31 | struct vcam_in_buffer buffers[2]; 32 | struct vcam_in_buffer dummy; 33 | struct vcam_in_buffer *pending; 34 | struct vcam_in_buffer *ready; 35 | }; 36 | 37 | struct vcam_out_buffer { 38 | struct vb2_v4l2_buffer vb; 39 | struct list_head list; 40 | size_t filled; 41 | }; 42 | 43 | struct vcam_out_queue { 44 | struct list_head active; 45 | int frame; 46 | /* TODO: implement more */ 47 | }; 48 | 49 | struct vcam_device_format { 50 | char *name; 51 | int fourcc; 52 | int bit_depth; 53 | }; 54 | 55 | struct vcam_device { 56 | dev_t dev_number; 57 | struct v4l2_device v4l2_dev; 58 | struct video_device vdev; 59 | 60 | /* input buffer */ 61 | struct vcam_in_queue in_queue; 62 | spinlock_t in_q_slock; 63 | spinlock_t in_fh_slock; 64 | bool fb_isopen; 65 | 66 | /* output buffer */ 67 | struct vb2_queue vb_out_vidq; 68 | struct vcam_out_queue vcam_out_vidq; 69 | spinlock_t out_q_slock; 70 | /* Output framerate */ 71 | struct v4l2_fract output_fps; 72 | 73 | /* Input framebuffer */ 74 | char vcam_fb_fname[FB_NAME_MAXLENGTH]; 75 | struct proc_dir_entry *vcam_fb_procf; 76 | struct mutex vcam_mutex; 77 | 78 | /* framebuffer private data */ 79 | void *fb_priv; 80 | 81 | /* Submitter thread */ 82 | struct task_struct *sub_thr_id; 83 | 84 | /* Format descriptor */ 85 | size_t nr_fmts; 86 | struct vcam_device_format out_fmts[PIXFMTS_MAX]; 87 | 88 | struct vcam_device_spec fb_spec; 89 | struct v4l2_pix_format output_format; 90 | struct v4l2_pix_format input_format; 91 | 92 | /* Conversion switches */ 93 | bool conv_pixfmt_on; 94 | bool conv_res_on; 95 | bool conv_crop_on; 96 | }; 97 | 98 | struct vcam_device *create_vcam_device(size_t idx, 99 | struct vcam_device_spec *dev_spec); 100 | int modify_vcam_device(struct vcam_device *vcam, 101 | struct vcam_device_spec *dev_spec); 102 | void destroy_vcam_device(struct vcam_device *vcam); 103 | 104 | int submitter_thread(void *data); 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /fb.c: -------------------------------------------------------------------------------- 1 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "fb.h" 10 | 11 | struct vcamfb_info { 12 | struct fb_info *info; 13 | void *addr; 14 | unsigned int offset; 15 | char name[FB_NAME_MAXLENGTH]; 16 | }; 17 | 18 | static int vcam_fb_open(struct fb_info *info, int user) 19 | { 20 | unsigned long flags = 0; 21 | 22 | struct vcam_device *dev = info->par; 23 | if (!dev) { 24 | pr_err("Private data field of PDE not initilized.\n"); 25 | return -ENODEV; 26 | } 27 | 28 | spin_lock_irqsave(&dev->in_fh_slock, flags); 29 | if (dev->fb_isopen) { 30 | spin_unlock_irqrestore(&dev->in_fh_slock, flags); 31 | return -EBUSY; 32 | } 33 | dev->fb_isopen = true; 34 | spin_unlock_irqrestore(&dev->in_fh_slock, flags); 35 | 36 | info->par = dev; 37 | 38 | return 0; 39 | } 40 | 41 | static void swap_in_queue_buffers(struct vcam_in_queue *q) 42 | { 43 | struct vcam_in_buffer *tmp; 44 | if (!q) 45 | return; 46 | tmp = q->pending; 47 | q->pending = q->ready; 48 | q->ready = tmp; 49 | q->pending->filled = 0; 50 | q->pending->xbar = 0; 51 | q->pending->ybar = 0; 52 | } 53 | 54 | static ssize_t vcam_fb_write(struct fb_info *info, 55 | const char __user *buffer, 56 | size_t length, 57 | loff_t *offset) 58 | { 59 | struct vcam_in_queue *in_q; 60 | struct vcam_in_buffer *buf; 61 | size_t copy_start; 62 | size_t to_be_copied; 63 | unsigned long flags = 0; 64 | void *data; 65 | size_t bytesperpixel; 66 | 67 | /* virtual resolution and min/max visible resolution's coordinates */ 68 | size_t line_vir, line_min, line_max; 69 | size_t y_vir, y_min, y_max; 70 | 71 | struct vcam_device *dev = info->par; 72 | if (!dev) { 73 | pr_err("Private data field of file not initialized yet.\n"); 74 | return 0; 75 | } 76 | 77 | in_q = &dev->in_queue; 78 | 79 | buf = in_q->pending; 80 | if (!buf) { 81 | pr_err("Pending pointer set to NULL\n"); 82 | return 0; 83 | } 84 | 85 | /* Reset buffer if last write is too old */ 86 | if ((buf->xbar || buf->ybar || buf->filled) && 87 | (((int32_t) jiffies - buf->jiffies) / HZ)) { 88 | pr_debug("Reseting jiffies, difference %d\n", 89 | ((int32_t) jiffies - buf->jiffies)); 90 | buf->filled = 0; 91 | buf->xbar = 0; 92 | buf->ybar = 0; 93 | } 94 | buf->jiffies = jiffies; 95 | 96 | /* Fill the buffer */ 97 | copy_start = 0; 98 | to_be_copied = length; 99 | 100 | data = buf->data; 101 | if (!data) { 102 | pr_err("NULL pointer to framebuffer"); 103 | return 0; 104 | } 105 | 106 | if (dev->input_format.pixelformat == V4L2_PIX_FMT_RGB24) { 107 | bytesperpixel = 3; 108 | } else if (dev->input_format.pixelformat == V4L2_PIX_FMT_YUYV) { 109 | bytesperpixel = 2; 110 | } 111 | line_vir = info->var.xres_virtual * bytesperpixel; 112 | line_min = info->var.xoffset * bytesperpixel; 113 | line_max = (info->var.xoffset + info->var.xres) * bytesperpixel; 114 | y_vir = info->var.yres_virtual; 115 | y_min = info->var.yoffset; 116 | y_max = (info->var.yoffset + info->var.yres); 117 | 118 | while (to_be_copied > 0 && buf->ybar < y_vir) { 119 | if (buf->ybar < y_min || buf->ybar >= y_max || buf->xbar >= line_max) { 120 | size_t remain = line_vir - buf->xbar; 121 | if (remain > to_be_copied) { 122 | buf->xbar += to_be_copied; 123 | break; 124 | } else { 125 | copy_start += remain; 126 | to_be_copied -= remain; 127 | buf->xbar = 0; 128 | buf->ybar += 1; 129 | } 130 | } else { 131 | if (buf->xbar < line_min) { 132 | size_t abandon = min(line_min - buf->xbar, to_be_copied); 133 | copy_start += abandon; 134 | to_be_copied -= abandon; 135 | buf->xbar += abandon; 136 | } else { 137 | size_t copyline = min(line_max - buf->xbar, to_be_copied); 138 | if (copy_from_user(data + buf->filled, 139 | (void __user *) (buffer + copy_start), 140 | copyline) != 0) { 141 | pr_warn("Failed to copy_from_user!"); 142 | } 143 | copy_start += copyline; 144 | to_be_copied -= copyline; 145 | buf->filled += copyline; 146 | buf->xbar += copyline; 147 | /* After data is copied, check if buf->xbar reaches the 148 | * border and needs to carry. 149 | */ 150 | if (buf->xbar == line_vir) { 151 | buf->xbar = 0; 152 | buf->ybar += 1; 153 | } 154 | } 155 | } 156 | } 157 | /* Check if buf->ybar reaches the border, which means the per-frame 158 | * information is complete. Swap the double buffer. 159 | */ 160 | if (buf->ybar == y_vir) { 161 | spin_lock_irqsave(&dev->in_q_slock, flags); 162 | swap_in_queue_buffers(in_q); 163 | spin_unlock_irqrestore(&dev->in_q_slock, flags); 164 | } 165 | 166 | return length; 167 | } 168 | 169 | 170 | static int vcam_fb_release(struct fb_info *info, int user) 171 | { 172 | unsigned long flags = 0; 173 | struct vcam_device *dev = info->par; 174 | 175 | spin_lock_irqsave(&dev->in_fh_slock, flags); 176 | dev->fb_isopen = false; 177 | spin_unlock_irqrestore(&dev->in_fh_slock, flags); 178 | dev->in_queue.pending->filled = 0; 179 | dev->in_queue.pending->xbar = 0; 180 | dev->in_queue.pending->ybar = 0; 181 | return 0; 182 | } 183 | 184 | static int vcam_fb_check_var(struct fb_var_screeninfo *var, 185 | struct fb_info *info) 186 | { 187 | int bpp; 188 | if (!var->xres) 189 | var->xres = 1; 190 | if (!var->yres) 191 | var->yres = 1; 192 | if (var->xres > var->xres_virtual) 193 | var->xres_virtual = var->xres; 194 | if (var->yres > var->yres_virtual) 195 | var->yres_virtual = var->yres; 196 | 197 | var->xoffset = (var->xres_virtual - var->xres) >> 1; 198 | var->yoffset = (var->yres_virtual - var->yres) >> 1; 199 | 200 | /* check bpp value ALIGN 8 */ 201 | bpp = var->bits_per_pixel; 202 | if (bpp <= 8 || bpp > 32) 203 | return -EINVAL; 204 | 205 | var->bits_per_pixel = ALIGN(bpp, 8); 206 | 207 | switch (var->bits_per_pixel) { 208 | case 16: 209 | if (var->transp.length) { 210 | /* RGBA 5551 */ 211 | var->red.offset = 0; 212 | var->red.length = 5; 213 | var->green.offset = 5; 214 | var->green.length = 5; 215 | var->blue.offset = 10; 216 | var->blue.length = 5; 217 | var->transp.offset = 15; 218 | var->transp.length = 1; 219 | } else { 220 | /* RGB 565 */ 221 | var->red.offset = 0; 222 | var->red.length = 5; 223 | var->green.offset = 5; 224 | var->green.length = 6; 225 | var->blue.offset = 11; 226 | var->blue.length = 5; 227 | var->transp.offset = 0; 228 | var->transp.length = 0; 229 | } 230 | break; 231 | case 24: 232 | /* RGB 888 */ 233 | var->red.offset = 0; 234 | var->red.length = 8; 235 | var->green.offset = 8; 236 | var->green.length = 8; 237 | var->blue.offset = 16; 238 | var->blue.length = 8; 239 | var->transp.offset = 0; 240 | var->transp.length = 0; 241 | break; 242 | case 32: 243 | /* RGBA 8888 */ 244 | var->red.offset = 0; 245 | var->red.length = 8; 246 | var->green.offset = 8; 247 | var->green.length = 8; 248 | var->blue.offset = 16; 249 | var->blue.length = 8; 250 | var->transp.offset = 24; 251 | var->transp.length = 8; 252 | break; 253 | } 254 | var->red.msb_right = 0; 255 | var->green.msb_right = 0; 256 | var->blue.msb_right = 0; 257 | var->transp.msb_right = 0; 258 | 259 | return 0; 260 | } 261 | 262 | static int vcam_fb_set_par(struct fb_info *info) 263 | { 264 | info->fix.visual = FB_VISUAL_TRUECOLOR; 265 | return 0; 266 | } 267 | 268 | static int vcam_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) 269 | { 270 | int ret = 271 | remap_vmalloc_range(vma, (void *) info->fix.smem_start, vma->vm_pgoff); 272 | if (ret < 0) 273 | return -EINVAL; 274 | ret = remap_vmalloc_range( 275 | vma, (void *) info->fix.smem_start + info->fix.smem_len, vma->vm_pgoff); 276 | if (ret < 0) 277 | return -EINVAL; 278 | return 0; 279 | } 280 | 281 | static int vcam_fb_setcolreg(u_int regno, 282 | u_int red, 283 | u_int green, 284 | u_int blue, 285 | u_int transp, 286 | struct fb_info *info) 287 | { 288 | if (regno >= 256) 289 | return -EINVAL; 290 | if (info->fix.visual == FB_VISUAL_TRUECOLOR) { 291 | u32 color; 292 | 293 | if (regno >= 16) 294 | return -EINVAL; 295 | 296 | color = (red << info->var.red.offset) | 297 | (green << info->var.green.offset) | 298 | (blue << info->var.blue.offset) | 299 | (transp << info->var.transp.offset); 300 | 301 | ((u32 *) (info->pseudo_palette))[regno] = color; 302 | } 303 | return 0; 304 | } 305 | 306 | static struct fb_ops vcamfb_ops = { 307 | .owner = THIS_MODULE, 308 | .fb_open = vcam_fb_open, 309 | .fb_release = vcam_fb_release, 310 | .fb_write = vcam_fb_write, 311 | .fb_set_par = vcam_fb_set_par, 312 | .fb_check_var = vcam_fb_check_var, 313 | .fb_setcolreg = vcam_fb_setcolreg, 314 | .fb_mmap = vcam_fb_mmap, 315 | }; 316 | 317 | static struct fb_fix_screeninfo vfb_fix = { 318 | .id = "vcamfb", 319 | .type = FB_TYPE_PACKED_PIXELS, 320 | .visual = FB_VISUAL_TRUECOLOR, 321 | .xpanstep = 1, 322 | .ypanstep = 1, 323 | .ywrapstep = 1, 324 | .accel = FB_ACCEL_NONE, 325 | }; 326 | 327 | static struct fb_var_screeninfo vfb_default = { 328 | .pixclock = 0, 329 | .left_margin = 0, 330 | .right_margin = 0, 331 | .upper_margin = 0, 332 | .lower_margin = 0, 333 | .hsync_len = 0, 334 | .vsync_len = 0, 335 | .vmode = FB_VMODE_NONINTERLACED, 336 | }; 337 | 338 | void set_crop_resolution(__u32 *width, 339 | __u32 *height, 340 | struct crop_ratio cropratio) 341 | { 342 | /* set the cropping rectangular resolution */ 343 | struct v4l2_rect crop = {0, 0, 0, 0}; 344 | struct v4l2_rect r = {0, 0, *width, *height}; 345 | struct v4l2_rect min_r = { 346 | 0, 0, r.width * cropratio.numerator / cropratio.denominator, 347 | r.height * cropratio.numerator / cropratio.denominator}; 348 | struct v4l2_rect max_r = {0, 0, r.width, r.height}; 349 | v4l2_rect_set_min_size(&crop, &min_r); 350 | v4l2_rect_set_max_size(&crop, &max_r); 351 | 352 | *width = crop.width; 353 | *height = crop.height; 354 | } 355 | 356 | int vcamfb_init(struct vcam_device *dev) 357 | { 358 | struct vcamfb_info *fb_data; 359 | struct vcam_in_queue *q = &dev->in_queue; 360 | struct fb_info *info; 361 | unsigned int size; 362 | int ret; 363 | 364 | /* malloc vcamfb_info */ 365 | fb_data = vmalloc(sizeof(struct vcamfb_info)); 366 | dev->fb_priv = (void *) fb_data; 367 | 368 | /* malloc fb_info */ 369 | fb_data->info = framebuffer_alloc(0, &dev->vdev.dev); 370 | info = fb_data->info; 371 | 372 | /* malloc framebuffer and init framebuffer */ 373 | size = dev->input_format.sizeimage * 2; 374 | if (!(fb_data->addr = vmalloc(size))) 375 | return -ENOMEM; 376 | fb_data->offset = dev->input_format.sizeimage; 377 | q->buffers[0].data = fb_data->addr; 378 | q->buffers[0].filled = 0; 379 | q->buffers[0].xbar = 0; 380 | q->buffers[0].ybar = 0; 381 | q->buffers[1].data = (void *) (fb_data->addr + fb_data->offset); 382 | q->buffers[1].filled = 0; 383 | q->buffers[1].xbar = 0; 384 | q->buffers[1].ybar = 0; 385 | memset(&q->dummy, 0, sizeof(struct vcam_in_buffer)); 386 | q->pending = &q->buffers[0]; 387 | q->ready = &q->buffers[1]; 388 | 389 | /* set the fb_fix */ 390 | vfb_fix.smem_len = dev->input_format.sizeimage; 391 | vfb_fix.smem_start = (unsigned long) fb_data->addr; 392 | vfb_fix.line_length = dev->input_format.bytesperline; 393 | 394 | /* set the fb_var */ 395 | vfb_default.xres = dev->fb_spec.width; 396 | vfb_default.yres = dev->fb_spec.height; 397 | vfb_default.bits_per_pixel = 24; 398 | vfb_default.xres_virtual = dev->fb_spec.xres_virtual; 399 | vfb_default.yres_virtual = dev->fb_spec.yres_virtual; 400 | vcam_fb_check_var(&vfb_default, info); 401 | 402 | /* set the fb_info */ 403 | info->screen_base = (char __iomem *) fb_data->addr; 404 | info->fix = vfb_fix; 405 | info->var = vfb_default; 406 | info->fbops = &vcamfb_ops; 407 | info->par = dev; 408 | info->pseudo_palette = NULL; 409 | INIT_LIST_HEAD(&info->modelist); 410 | 411 | /* set the fb_cmap */ 412 | info->cmap.red = NULL; 413 | info->cmap.green = NULL; 414 | info->cmap.blue = NULL; 415 | info->cmap.transp = NULL; 416 | 417 | if (fb_alloc_cmap(&info->cmap, 256, 0)) { 418 | pr_err("Failed to allocate cmap!"); 419 | return -ENOMEM; 420 | } 421 | 422 | ret = register_framebuffer(info); 423 | if (ret < 0) 424 | goto fb_alloc_failure; 425 | 426 | snprintf(fb_data->name, sizeof(fb_data->name), "fb%d", 427 | MINOR(info->dev->devt)); 428 | 429 | return 0; 430 | 431 | fb_alloc_failure: 432 | fb_dealloc_cmap(&info->cmap); 433 | framebuffer_release(info); 434 | return -EINVAL; 435 | } 436 | 437 | void vcamfb_destroy(struct vcam_device *dev) 438 | { 439 | struct vcamfb_info *fb_data = (struct vcamfb_info *) dev->fb_priv; 440 | struct fb_info *info; 441 | 442 | if (!fb_data) 443 | return; 444 | 445 | info = fb_data->info; 446 | if (info) { 447 | unregister_framebuffer(info); 448 | fb_dealloc_cmap(&info->cmap); 449 | framebuffer_release(info); 450 | } 451 | 452 | vfree(fb_data->addr); 453 | vfree(fb_data); 454 | } 455 | 456 | void vcamfb_update(struct vcam_device *dev) 457 | { 458 | struct vcamfb_info *fb_data = (struct vcamfb_info *) dev->fb_priv; 459 | struct fb_info *info = fb_data->info; 460 | struct vcam_in_queue *q = &dev->in_queue; 461 | 462 | /* remalloc the framebuffer and vcam_in_queue */ 463 | if (info->fix.smem_len != dev->input_format.sizeimage) { 464 | unsigned int size; 465 | vfree(fb_data->addr); 466 | fb_data->offset = dev->input_format.sizeimage; 467 | size = dev->input_format.sizeimage * 2; 468 | fb_data->addr = vmalloc(size); 469 | q->buffers[0].data = fb_data->addr; 470 | q->buffers[1].data = (void *) (fb_data->addr + fb_data->offset); 471 | q->buffers[0].filled = 0; 472 | q->buffers[0].xbar = 0; 473 | q->buffers[0].ybar = 0; 474 | q->buffers[1].filled = 0; 475 | q->buffers[1].xbar = 0; 476 | q->buffers[1].ybar = 0; 477 | memset(&q->dummy, 0, sizeof(struct vcam_in_buffer)); 478 | 479 | /* reset the fb_fix */ 480 | info->fix.smem_len = dev->input_format.sizeimage; 481 | info->fix.smem_start = (unsigned long) fb_data->addr; 482 | info->fix.line_length = dev->input_format.bytesperline; 483 | 484 | /* reset the fb_info */ 485 | info->screen_base = (char __iomem *) fb_data->addr; 486 | 487 | /* reset the fb_var */ 488 | info->var.xres = dev->fb_spec.width; 489 | info->var.yres = dev->fb_spec.height; 490 | info->var.xres_virtual = dev->fb_spec.xres_virtual; 491 | info->var.yres_virtual = dev->fb_spec.yres_virtual; 492 | info->var.xoffset = (info->var.xres_virtual - info->var.xres) >> 1; 493 | info->var.yoffset = (info->var.yres_virtual - info->var.yres) >> 1; 494 | } 495 | } 496 | 497 | char *vcamfb_get_devnode(struct vcam_device *dev) 498 | { 499 | struct vcamfb_info *fb_data; 500 | fb_data = dev->fb_priv; 501 | return fb_data->name; 502 | } 503 | -------------------------------------------------------------------------------- /fb.h: -------------------------------------------------------------------------------- 1 | #ifndef VCAM_FB_H 2 | #define VCAM_FB_H 3 | 4 | #include "device.h" 5 | 6 | void set_crop_resolution(__u32 *width, 7 | __u32 *height, 8 | struct crop_ratio cropratio); 9 | 10 | int vcamfb_init(struct vcam_device *dev); 11 | 12 | void vcamfb_destroy(struct vcam_device *dev); 13 | 14 | void vcamfb_update(struct vcam_device *dev); 15 | 16 | char *vcamfb_get_devnode(struct vcam_device *dev); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "control.h" 6 | 7 | MODULE_LICENSE("Dual MIT/GPL"); 8 | MODULE_AUTHOR("National Cheng Kung University, Taiwan"); 9 | MODULE_DESCRIPTION("Virtual V4L2 compatible camera device driver"); 10 | 11 | #define CONTROL_DEV_NAME "vcamctl" 12 | #define VCAM_DEV_NAME "vcam" 13 | 14 | unsigned short devices_max = 8; 15 | unsigned short create_devices = 1; 16 | unsigned char allow_pix_conversion = 0; 17 | unsigned char allow_scaling = 0; 18 | unsigned char allow_cropping = 0; 19 | 20 | module_param(devices_max, ushort, 0); 21 | MODULE_PARM_DESC(devices_max, "Maximal number of devices\n"); 22 | 23 | module_param(create_devices, ushort, 0); 24 | MODULE_PARM_DESC(create_devices, 25 | "Number of devices to be created during initialization\n"); 26 | 27 | module_param(allow_pix_conversion, byte, 0); 28 | MODULE_PARM_DESC(allow_pix_conversion, 29 | "Allow pixel format conversion by default\n"); 30 | 31 | module_param(allow_scaling, byte, 0); 32 | MODULE_PARM_DESC(allow_scaling, "Allow image scaling by default\n"); 33 | 34 | module_param(allow_cropping, byte, 0); 35 | MODULE_PARM_DESC(allow_cropping, "Allow image cropping by default\n"); 36 | 37 | const char *vcam_dev_name = VCAM_DEV_NAME; 38 | 39 | static int __init vcam_init(void) 40 | { 41 | int i; 42 | int ret = create_control_device(CONTROL_DEV_NAME); 43 | if (ret) 44 | goto failure; 45 | 46 | for (i = 0; i < create_devices; i++) 47 | request_vcam_device(NULL); 48 | 49 | failure: 50 | return ret; 51 | } 52 | 53 | static void __exit vcam_exit(void) 54 | { 55 | destroy_control_device(); 56 | } 57 | 58 | module_init(vcam_init); 59 | module_exit(vcam_exit); 60 | -------------------------------------------------------------------------------- /udev/66-vcmod.rules: -------------------------------------------------------------------------------- 1 | ACTION=="add", KERNEL=="vcamctl", MODE="0774", GROUP="video" 2 | -------------------------------------------------------------------------------- /vcam-util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "vcam.h" 12 | 13 | static const char *short_options = "hcm:r:ls:p:d:"; 14 | 15 | const struct option long_options[] = { 16 | {"help", 0, NULL, 'h'}, {"create", 0, NULL, 'c'}, 17 | {"modify", 1, NULL, 'm'}, {"list", 0, NULL, 'l'}, 18 | {"size", 1, NULL, 's'}, {"pixfmt", 1, NULL, 'p'}, 19 | {"device", 1, NULL, 'd'}, {"remove", 1, NULL, 'r'}, 20 | {NULL, 0, NULL, 0}}; 21 | 22 | const char *help = 23 | " -h --help Print this informations.\n" 24 | " -c --create Create a new device.\n" 25 | " -m --modify idx Modify a device.\n" 26 | " -r --remove idx Remove a device.\n" 27 | " -l --list List devices.\n" 28 | " -s --size WIDTHxHEIGHTxCROPRATIO Specify virtual resolution or crop " 29 | "ratio.\n" 30 | " Crop ratio will only be applied in " 31 | "cropping mode.\n" 32 | " For instance:\n" 33 | " WxH: 640x480 Specify the virtual resolution.\n" 34 | " CR: 5/6 Apply crop ratio to the current " 35 | "resolution.\n" 36 | " WxHxCR: 640x480x5/6 Specify the virtual resolution " 37 | "and apply with crop ratio.\n" 38 | "\n" 39 | " -p --pixfmt pix_fmt Specify pixel format (rgb24,yuyv).\n" 40 | " -d --device /dev/* Control device node.\n"; 41 | 42 | enum ACTION { ACTION_NONE, ACTION_CREATE, ACTION_DESTROY, ACTION_MODIFY }; 43 | 44 | struct vcam_device_spec device_template = { 45 | .width = 640, 46 | .height = 480, 47 | .pix_fmt = VCAM_PIXFMT_RGB24, 48 | .video_node = "", 49 | .fb_node = "", 50 | }; 51 | 52 | static char ctl_path[128] = "/dev/vcamctl"; 53 | 54 | static bool parse_cropratio(char *res_str, struct vcam_device_spec *dev) 55 | { 56 | struct crop_ratio cropratio; 57 | char *tmp = strtok(res_str, "/:,"); 58 | if (!tmp) 59 | return false; 60 | 61 | cropratio.numerator = atoi(tmp); 62 | tmp = strtok(NULL, "/:,"); 63 | if (!tmp) 64 | return false; 65 | cropratio.denominator = atoi(tmp); 66 | 67 | if (cropratio.numerator > cropratio.denominator || 68 | cropratio.denominator == 0) 69 | return false; 70 | dev->cropratio = cropratio; 71 | return true; 72 | } 73 | 74 | bool parse_resolution(char *res_str, struct vcam_device_spec *dev) 75 | { 76 | /* Check resolution format */ 77 | char *tmp = strtok(res_str, "x:,"); 78 | if (!tmp) 79 | return false; 80 | __u32 width = atoi(tmp); 81 | tmp = strtok(NULL, "x:,"); 82 | /* Not comform to resolution format. Check crop ratio format */ 83 | if (!tmp) 84 | return parse_cropratio(res_str, dev); 85 | dev->width = width; 86 | dev->height = atoi(tmp); 87 | 88 | /* Check crop ratio format */ 89 | tmp = strtok(NULL, "x:,"); 90 | if (tmp) { 91 | return parse_cropratio(tmp, dev); 92 | } 93 | return true; 94 | } 95 | 96 | int determine_pixfmt(char *pixfmt_str) 97 | { 98 | if (!strncmp(pixfmt_str, "rgb24", 5)) 99 | return VCAM_PIXFMT_RGB24; 100 | if (!strncmp(pixfmt_str, "yuyv", 4)) 101 | return VCAM_PIXFMT_YUYV; 102 | return -1; 103 | } 104 | 105 | int create_device(struct vcam_device_spec *dev) 106 | { 107 | int fd = open(ctl_path, O_RDWR); 108 | if (fd == -1) { 109 | fprintf(stderr, "Failed to open %s device.\n", ctl_path); 110 | return -1; 111 | } 112 | 113 | if (!dev->width || !dev->height) { 114 | dev->width = device_template.width; 115 | dev->height = device_template.height; 116 | } 117 | 118 | if (!dev->pix_fmt) 119 | dev->pix_fmt = device_template.pix_fmt; 120 | 121 | int res = ioctl(fd, VCAM_IOCTL_CREATE_DEVICE, dev); 122 | if (res) { 123 | fprintf(stderr, "Failed to create a new device.\n"); 124 | } 125 | 126 | close(fd); 127 | return res; 128 | } 129 | 130 | int remove_device(struct vcam_device_spec *dev) 131 | { 132 | int fd = open(ctl_path, O_RDWR); 133 | if (fd == -1) { 134 | fprintf(stderr, "Failed to open %s device.\n", ctl_path); 135 | return -1; 136 | } 137 | 138 | int res = ioctl(fd, VCAM_IOCTL_DESTROY_DEVICE, dev); 139 | if (res) { 140 | fprintf(stderr, "Failed to remove the device on index %d.\n", 141 | dev->idx + 1); 142 | } 143 | 144 | close(fd); 145 | return res; 146 | } 147 | 148 | int modify_device(struct vcam_device_spec *dev) 149 | { 150 | struct vcam_device_spec orig_dev = {.idx = dev->idx}; 151 | 152 | int fd = open(ctl_path, O_RDWR); 153 | if (fd == -1) { 154 | fprintf(stderr, "Failed to open %s device.\n", ctl_path); 155 | return -1; 156 | } 157 | 158 | if (ioctl(fd, VCAM_IOCTL_GET_DEVICE, &orig_dev)) { 159 | fprintf(stderr, "Failed to find device on index %d.\n", 160 | orig_dev.idx + 1); 161 | close(fd); 162 | return -1; 163 | } 164 | 165 | if (!dev->width || !dev->height) { 166 | dev->width = orig_dev.width; 167 | dev->height = orig_dev.height; 168 | } 169 | 170 | if (!dev->pix_fmt) 171 | dev->pix_fmt = orig_dev.pix_fmt; 172 | 173 | if (!dev->cropratio.numerator || !dev->cropratio.denominator) 174 | dev->cropratio = orig_dev.cropratio; 175 | 176 | int res = ioctl(fd, VCAM_IOCTL_MODIFY_SETTING, dev); 177 | if (res) { 178 | fprintf(stderr, "Failed to modify the device.\n"); 179 | } 180 | 181 | close(fd); 182 | return res; 183 | } 184 | 185 | int list_devices() 186 | { 187 | struct vcam_device_spec dev = {.idx = 0}; 188 | 189 | int fd = open(ctl_path, O_RDWR); 190 | if (fd == -1) { 191 | fprintf(stderr, "Failed to open %s device.\n", ctl_path); 192 | return -1; 193 | } 194 | 195 | printf("Available virtual V4L2 compatible devices:\n"); 196 | while (!ioctl(fd, VCAM_IOCTL_GET_DEVICE, &dev)) { 197 | dev.idx++; 198 | printf("%d. %s(%d,%d,%d/%d,%s) -> %s\n", dev.idx, dev.fb_node, 199 | dev.width, dev.height, dev.cropratio.numerator, 200 | dev.cropratio.denominator, 201 | dev.pix_fmt == VCAM_PIXFMT_RGB24 ? "rgb24" : "yuyv", 202 | dev.video_node); 203 | } 204 | close(fd); 205 | return 0; 206 | } 207 | 208 | int main(int argc, char *argv[]) 209 | { 210 | int next_option; 211 | enum ACTION current_action = ACTION_NONE; 212 | struct vcam_device_spec dev; 213 | int ret = 0; 214 | int tmp; 215 | 216 | memset(&dev, 0x00, sizeof(struct vcam_device_spec)); 217 | 218 | /* Process command line options */ 219 | do { 220 | next_option = 221 | getopt_long(argc, argv, short_options, long_options, NULL); 222 | switch (next_option) { 223 | case 'h': 224 | printf("%s", help); 225 | exit(0); 226 | case 'c': 227 | current_action = ACTION_CREATE; 228 | printf("Creating a new device.\n"); 229 | break; 230 | case 'm': 231 | current_action = ACTION_MODIFY; 232 | dev.idx = atoi(optarg) - 1; 233 | break; 234 | case 'r': 235 | current_action = ACTION_DESTROY; 236 | dev.idx = atoi(optarg) - 1; 237 | printf("Removing the device.\n"); 238 | break; 239 | case 'l': 240 | list_devices(); 241 | break; 242 | case 's': 243 | if (!parse_resolution(optarg, &dev)) { 244 | fprintf(stderr, "Failed to parse resolution and crop ratio.\n"); 245 | exit(-1); 246 | } 247 | printf("Setting resolution to %dx%dx%d/%d.\n", dev.width, 248 | dev.height, dev.cropratio.numerator, 249 | dev.cropratio.denominator); 250 | break; 251 | case 'p': 252 | tmp = determine_pixfmt(optarg); 253 | if (tmp < 0) { 254 | fprintf(stderr, "Failed to recognize pixel format %s.\n", 255 | optarg); 256 | exit(-1); 257 | } 258 | dev.pix_fmt = (char) tmp; 259 | printf("Setting pixel format to %s.\n", optarg); 260 | break; 261 | case 'd': 262 | printf("Using device %s.\n", optarg); 263 | strncpy(ctl_path, optarg, sizeof(ctl_path) - 1); 264 | break; 265 | } 266 | } while (next_option != -1); 267 | 268 | switch (current_action) { 269 | case ACTION_CREATE: 270 | ret = create_device(&dev); 271 | break; 272 | case ACTION_DESTROY: 273 | ret = remove_device(&dev); 274 | break; 275 | case ACTION_MODIFY: 276 | ret = modify_device(&dev); 277 | break; 278 | case ACTION_NONE: 279 | break; 280 | } 281 | 282 | return ret; 283 | } 284 | -------------------------------------------------------------------------------- /vcam.h: -------------------------------------------------------------------------------- 1 | #ifndef VCAM_H 2 | #define VCAM_H 3 | 4 | #include 5 | 6 | #define VCAM_IOCTL_CREATE_DEVICE 0x111 7 | #define VCAM_IOCTL_DESTROY_DEVICE 0x222 8 | #define VCAM_IOCTL_GET_DEVICE 0x333 9 | #define VCAM_IOCTL_ENUM_DEVICES 0x444 10 | #define VCAM_IOCTL_MODIFY_SETTING 0x555 11 | 12 | typedef enum { VCAM_PIXFMT_RGB24 = 0x01, VCAM_PIXFMT_YUYV = 0x02 } pixfmt_t; 13 | 14 | struct crop_ratio { 15 | __u32 numerator; 16 | __u32 denominator; 17 | }; 18 | 19 | struct vcam_device_spec { 20 | unsigned int idx; 21 | 22 | /* virtual resolution */ 23 | __u32 xres_virtual, yres_virtual; 24 | /* resolution */ 25 | __u32 width, height; 26 | 27 | struct crop_ratio cropratio; 28 | 29 | pixfmt_t pix_fmt; 30 | char video_node[64]; 31 | char fb_node[64]; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /videobuf.c: -------------------------------------------------------------------------------- 1 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "videobuf.h" 9 | 10 | static int vcam_out_queue_setup(struct vb2_queue *vq, 11 | unsigned int *nbuffers, 12 | unsigned int *nplanes, 13 | unsigned int sizes[], 14 | struct device *alloc_ctxs[]) 15 | { 16 | int i; 17 | struct vcam_device *dev = vb2_get_drv_priv(vq); 18 | unsigned long size = dev->output_format.sizeimage; 19 | 20 | if (*nbuffers < 2) 21 | *nbuffers = 2; 22 | 23 | if (*nplanes > 0) { 24 | if (sizes[0] < size) 25 | return -EINVAL; 26 | return 0; 27 | } 28 | 29 | *nplanes = 1; 30 | 31 | sizes[0] = size; 32 | for (i = 1; i < VB2_MAX_PLANES; ++i) 33 | sizes[i] = 0; 34 | pr_debug("queue_setup completed\n"); 35 | return 0; 36 | } 37 | 38 | static int vcam_out_buffer_prepare(struct vb2_buffer *vb) 39 | { 40 | struct vcam_device *dev = vb2_get_drv_priv(vb->vb2_queue); 41 | unsigned long size = dev->output_format.sizeimage; 42 | if (vb2_plane_size(vb, 0) < size) { 43 | pr_err(KERN_ERR "data will not fit into buffer\n"); 44 | return -EINVAL; 45 | } 46 | 47 | vb2_set_plane_payload(vb, 0, size); 48 | return 0; 49 | } 50 | 51 | static void vcam_out_buffer_queue(struct vb2_buffer *vb) 52 | { 53 | unsigned long flags = 0; 54 | struct vcam_device *dev = vb2_get_drv_priv(vb->vb2_queue); 55 | struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 56 | struct vcam_out_buffer *buf = 57 | container_of(vbuf, struct vcam_out_buffer, vb); 58 | struct vcam_out_queue *q = &dev->vcam_out_vidq; 59 | buf->filled = 0; 60 | 61 | spin_lock_irqsave(&dev->out_q_slock, flags); 62 | list_add_tail(&buf->list, &q->active); 63 | spin_unlock_irqrestore(&dev->out_q_slock, flags); 64 | } 65 | 66 | static int vcam_start_streaming(struct vb2_queue *q, unsigned int count) 67 | { 68 | struct vcam_device *dev = q->drv_priv; 69 | 70 | /* Try to start kernel thread */ 71 | dev->sub_thr_id = kthread_create(submitter_thread, dev, "vcam_submitter"); 72 | if (!dev->sub_thr_id) { 73 | pr_err("Failed to create kernel thread\n"); 74 | return -ECANCELED; 75 | } 76 | 77 | wake_up_process(dev->sub_thr_id); 78 | 79 | return 0; 80 | } 81 | 82 | static void vcam_stop_streaming(struct vb2_queue *vb2_q) 83 | { 84 | struct vcam_device *dev = vb2_q->drv_priv; 85 | struct vcam_out_queue *q = &dev->vcam_out_vidq; 86 | unsigned long flags = 0; 87 | 88 | /* Stop running threads */ 89 | if (dev->sub_thr_id) 90 | kthread_stop(dev->sub_thr_id); 91 | 92 | dev->sub_thr_id = NULL; 93 | /* Empty buffer queue */ 94 | spin_lock_irqsave(&dev->out_q_slock, flags); 95 | while (!list_empty(&q->active)) { 96 | struct vcam_out_buffer *buf = 97 | list_entry(q->active.next, struct vcam_out_buffer, list); 98 | list_del(&buf->list); 99 | vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 100 | pr_debug("Throwing out buffer\n"); 101 | } 102 | spin_unlock_irqrestore(&dev->out_q_slock, flags); 103 | } 104 | 105 | static void vcam_outbuf_lock(struct vb2_queue *vq) 106 | { 107 | struct vcam_device *dev = vb2_get_drv_priv(vq); 108 | mutex_lock(&dev->vcam_mutex); 109 | } 110 | 111 | static void vcam_outbuf_unlock(struct vb2_queue *vq) 112 | { 113 | struct vcam_device *dev = vb2_get_drv_priv(vq); 114 | mutex_unlock(&dev->vcam_mutex); 115 | } 116 | 117 | static const struct vb2_ops vcam_vb2_ops = { 118 | .queue_setup = vcam_out_queue_setup, 119 | .buf_prepare = vcam_out_buffer_prepare, 120 | .buf_queue = vcam_out_buffer_queue, 121 | .start_streaming = vcam_start_streaming, 122 | .stop_streaming = vcam_stop_streaming, 123 | .wait_prepare = vcam_outbuf_unlock, 124 | .wait_finish = vcam_outbuf_lock, 125 | }; 126 | 127 | int vcam_out_videobuf2_setup(struct vcam_device *dev) 128 | { 129 | struct vb2_queue *q = &dev->vb_out_vidq; 130 | 131 | q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 132 | q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; 133 | q->drv_priv = dev; 134 | q->buf_struct_size = sizeof(struct vcam_out_buffer); 135 | q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; 136 | q->ops = &vcam_vb2_ops; 137 | q->mem_ops = &vb2_vmalloc_memops; 138 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) 139 | q->min_queued_buffers = 2; 140 | #else 141 | q->min_buffers_needed = 2; 142 | #endif 143 | q->lock = &dev->vcam_mutex; 144 | 145 | return vb2_queue_init(q); 146 | } 147 | -------------------------------------------------------------------------------- /videobuf.h: -------------------------------------------------------------------------------- 1 | #ifndef VCAM_VIDEOBUF_H 2 | #define VCAM_VIDEOBUF_H 3 | 4 | #include "device.h" 5 | 6 | int vcam_out_videobuf2_setup(struct vcam_device *dev); 7 | 8 | #endif 9 | --------------------------------------------------------------------------------