/** mmvideo.c * * author marco corvi * date may 2003 * * credits: after the stv680 driver (thanks) * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mmvideo.h" #define DRIVER_VERSION "v0.10" #define DRIVER_AUTHOR "marco corvi " #define DRIVER_DESC "memory based video driver" #define MMVIDEO_FPS 20 /* 10 frames per sec */ MODULE_AUTHOR( DRIVER_AUTHOR ); MODULE_DESCRIPTION( DRIVER_DESC ); #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 4, 10) MODULE_LICENSE( "GPL" ); #endif EXPORT_NO_SYMBOLS; // #define VIDEO_DEVICE_HAVE_OWNER #define PDEBUG( fmt, args... ) printk(KERN_ALERT fmt, ## args ); /* ================================================================ */ /* virtual memory */ /* ---------------------------------------------------------------- */ static inline unsigned long uvirt_to_kva( pgd_t * pgd, unsigned long addr ) { unsigned long ret = 0UL; pmd_t * pmd; pte_t * pte; if ( !pgd_none(*pgd)) { pmd = pmd_offset( pgd, addr ); if ( !pmd_none( *pmd )) { pte = pte_offset( pmd, addr ); if ( pte_present( *pte ) ) { ret = (unsigned long) page_address( pte_page( *pte ) ); ret |= (addr & (PAGE_SIZE - 1)); } } } return ret; } static inline unsigned long kvirt_to_pa( unsigned long addr ) { unsigned long va, kva, ret; va = VMALLOC_VMADDR( addr ); // recast unsigned long kva = uvirt_to_kva( pgd_offset_k(va), va ); // (init->mm)->pgd + pgd_index( va ) ret = __pa( kva ); // kva - PAGE_OFFSET return ret; } static void * rvmalloc( unsigned long size ) { void * mem; unsigned long addr, page; size = (size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1); mem = vmalloc_32( size ); if ( ! mem ) return NULL; memset( mem, 0, size ); addr = (unsigned long) mem; while ( size > 0 ) { page = kvirt_to_pa( addr ); mem_map_reserve( virt_to_page( __va( page ) ) ); addr += PAGE_SIZE; if ( size > PAGE_SIZE ) size -= PAGE_SIZE; else size = 0; } return mem; } static void rvfree( void * mem, unsigned long size ) { unsigned long addr, page; if ( ! mem ) return; size = (size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1); addr = (unsigned long)mem; while ( size > 0 ) { page = kvirt_to_pa( addr ); mem_map_unreserve( virt_to_page( __va( page ) ) ); addr += PAGE_SIZE; if ( size > PAGE_SIZE ) size -= PAGE_SIZE; else size = 0; } vfree( mem ); } /* ---------------------------------------------------------------- */ /* hardware */ /* ---------------------------------------------------------------- */ static int mmvideo_hw_reset( struct mmvideo_device * mvdev ) { // real hardware initialization // return -1; if something goes wrong mvdev->total_cnt = 0; // init statistics mvdev->error_cnt = 0; mvdev->frame_cnt = 0; mvdev->mmap_cnt = 0; mvdev->drop_cnt = 0; mvdev->vpict.brightness = 32767; mvdev->vpict.whiteness = 0; mvdev->vpict.colour = 32767; mvdev->vpict.contrast = 32767; mvdev->vpict.hue = 32767; mvdev->vpict.palette = MMVIDEO_PALETTE; mvdev->vpict.depth = MMVIDEO_DEPTH; mvdev->cwidth = MMVIDEO_WIDTH; mvdev->cheight = MMVIDEO_HEIGHT; mvdev->vwidth = MMVIDEO_WIDTH; mvdev->vheight = MMVIDEO_HEIGHT; return 0; } static void mmvideo_hw_interrupt( struct mmvideo_device * mvdev ) { int i; // printk("mmvideo_hw_interrupt() frame %d\n", mvdev->hw_frame_nr); mvdev->frame_to_read = -1; for (i=0; iframe_nr = (mvdev->frame_nr + 1) % MMVIDEO_NUMFRAMES; if ( ( mvdev->frame_flag[mvdev->frame_nr] & MMVIDEO_FRAME_MMAP ) && ( mvdev->frame_flag[mvdev->frame_nr] & MMVIDEO_FRAME_MMAPUSED ) ) { down( & mvdev->lock ); memcpy( mvdev->frame[mvdev->frame_nr].data, mvdev->hw_frame[ mvdev->hw_frame_nr ].data, MMVIDEO_FRAMESIZE ); mvdev->frame_flag[mvdev->frame_nr] &= ~MMVIDEO_FRAME_MMAPUSED; up( & mvdev->lock ); // increase counter of mmaped frames mvdev->mmap_cnt ++; break; } } if ( i == MMVIDEO_NUMFRAMES ) { mvdev->frame_to_read = mvdev->hw_frame_nr; wake_up_interruptible( &mvdev->wq ); // wake up any waiting process } // increase total number of frames mvdev->total_cnt ++; } // compute the new frame (hardware real acquisition) // void mmvideo_hw_newframe( unsigned long arg ) { struct mmvideo_device * mvdev = (struct mmvideo_device * )arg; int i,j; int f0 = mvdev->hw_frame_nr; int f1 = (f0+1)%2; // printk("mmvideo_hw_newframe %d (old %d)\n", f0, f1); down( & mvdev->lock ); { unsigned char * r0 = mvdev->hw_frame[f0].data; unsigned char * r1 = mvdev->hw_frame[f1].data; unsigned char * g0 = r0+1; unsigned char * g1 = r1+1; unsigned char * b0 = r0+2; unsigned char * b1 = r1+2; unsigned char rtmp, gtmp, btmp; for (j=0; jhw_frame_nr = f1; up( & mvdev->lock ); mod_timer( &mvdev->timer, jiffies+HZ/MMVIDEO_FPS ); // schedule next capture mmvideo_hw_interrupt( mvdev ); } static int mmvideo_hw_mrelease( struct mmvideo_device * mvdev, int frame ) { down( & mvdev->lock ); if ( ! (mvdev->frame_flag[frame] & MMVIDEO_FRAME_MMAP) ) { up( & mvdev->lock ); return -EINVAL; } mvdev->frame_flag[frame] |= MMVIDEO_FRAME_MMAPUSED; up( & mvdev->lock ); return 0; } // there should be a way to end the mmap capture // and let the driver reset overwrite to 1 // this should be done at the munmap() syscall, but the driver is // not invoked at that time ... static int mmvideo_hw_mcapture( struct mmvideo_device * mvdev, int frame ) { int i; down( & mvdev->lock ); for (i=0; iframe_flag[i] |= ( MMVIDEO_FRAME_MMAP | MMVIDEO_FRAME_MMAPUSED ); up( & mvdev->lock ); return 0; } static void mmvideo_hw_start( struct mmvideo_device * mvdev ) { int i; for (i=0; iframe_flag[i] = MMVIDEO_FRAME_READY; mvdev->frame_nr = 0; // start at frame 0 init_timer( &mvdev->timer ); mvdev->timer.function = mmvideo_hw_newframe; mvdev->timer.expires = jiffies + HZ/MMVIDEO_FPS; mvdev->timer.data = (unsigned long)mvdev; add_timer( &mvdev->timer ); } static void mmvideo_hw_stop( struct mmvideo_device * mvdev ) { int i; del_timer_sync( &mvdev->timer ); for (i=0; iframe_flag[i] &= MMVIDEO_FRAME_READY; mvdev->frame_nr = -1; } /* ---------------------------------------------------------------- */ /* proc fs */ /* ---------------------------------------------------------------- */ #if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) static struct proc_dir_entry * mmvideo_proc_dir = NULL; extern struct proc_dir_entry * video_proc_entry; static int mmvideo_proc_read( char * page, char** start, off_t off, int cnt, int * eof, void * data ) { char * out = page; long len; struct mmvideo_device * mvdev = (struct mmvideo_device *)data; out += sprintf(out, "Version : %s\n", DRIVER_VERSION); out += sprintf(out, "In use : %d\n", mvdev->user); out += sprintf(out, "Nr frames : %d\n", MMVIDEO_NUMFRAMES); out += sprintf(out, "Size : %dx%d\n", mvdev->vwidth, mvdev->vheight); out += sprintf(out, "Palette : %d\n", mvdev->vpict.palette ); out += sprintf(out, "Frame total : %d\n", mvdev->total_cnt); out += sprintf(out, "Frame read : %d\n", mvdev->frame_cnt); out += sprintf(out, "Frame mmap : %d\n", mvdev->mmap_cnt); out += sprintf(out, "Error : %d\n", mvdev->error_cnt); out += sprintf(out, "Dropped : %d\n", mvdev->drop_cnt); len = out - page; len -= off; if (len < cnt) { *eof = 1; if (len < 0) return 0; } else { len = cnt; } *start = page + off; return len; } static int mmvideo_proc_create_entry( struct mmvideo_device * mvdev ) { char name[12]; struct proc_dir_entry * ent; if ( ! mmvideo_proc_dir || ! mvdev) return -1; sprintf(name, "mmvideo%d", mvdev->vdev.minor); ent = create_proc_entry(name, S_IFREG | S_IRUGO | S_IWUSR, mmvideo_proc_dir); if ( ! ent ) return -1; ent->data = mvdev; // copy address of mmvideo_device into 'data' ent->read_proc = mmvideo_proc_read; mvdev->proc_entry = ent; return 0; } static void mmvideo_proc_destroy_entry( struct mmvideo_device * mvdev ) { char name[12]; if ( ! mmvideo_proc_dir || ! mvdev) return; sprintf(name, "mmvideo%d", mvdev->vdev.minor); remove_proc_entry( name, mmvideo_proc_dir); mvdev->proc_entry = NULL; } static int mmvideo_proc_create_dir( void ) { if ( ! video_proc_entry ) return -1; mmvideo_proc_dir = create_proc_entry("mmvideo", S_IFDIR, video_proc_entry); if ( mmvideo_proc_dir ) { mmvideo_proc_dir->owner = THIS_MODULE; } else { return -1; } return 0; } static void mmvideo_proc_destroy_dir( void ) { if ( ! video_proc_entry ) return; remove_proc_entry( "mmvideo", video_proc_entry); } #endif // defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) /* ---------------------------------------------------------------- */ /* video 4 linux */ /* ---------------------------------------------------------------- */ static int mmvideo_v4l_open( struct video_device * vdev, int flags ) { struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); int err = 0; // only one user at a time if ( mvdev->user > 0 ) { return -EINVAL; } MOD_INC_USE_COUNT; mvdev->user = 1; // initialize hw and device struct err = mmvideo_hw_reset( mvdev ); if ( err ) { MOD_DEC_USE_COUNT; mvdev->user = 0; } // start hardware video streaming mmvideo_hw_start( mvdev ); return err; } static void mmvideo_v4l_close( struct video_device * vdev ) { // called with BKL held struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); // stop hardware video-streaming mmvideo_hw_stop( mvdev ); mvdev->user = 0; MOD_DEC_USE_COUNT; } static long mmvideo_v4l_write( struct video_device * vdev, const char * buf, unsigned long cnt, int noblock ) { struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); if ( cnt != MMVIDEO_FRAMESIZE ) return -EINVAL; down( & mvdev->lock ); if ( copy_from_user( mvdev->hw_frame[mvdev->hw_frame_nr].data, buf, cnt ) != 0 ) { up( & mvdev->lock ); return -EFAULT; } // mvdev->frame_index = 0; up( & mvdev->lock ); return MMVIDEO_FRAMESIZE; } static long mmvideo_v4l_read( struct video_device * vdev, char * buf, unsigned long cnt, int noblock ) { struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); DECLARE_WAITQUEUE( wait, current ); // struct file * file = vdev->file; // clipping not supported if ( cnt < MMVIDEO_FRAMESIZE ) return -EINVAL; /* pre-sleep */ add_wait_queue( & mvdev->wq, & wait ); current->state = TASK_INTERRUPTIBLE; down( & mvdev->lock ); while ( mvdev->frame_to_read < 0 ) { // printk("mmvideo_v4l_read frame_to_read %d wait\n", // mvdev->frame_to_read ); up( & mvdev->lock ); // if ( file->flags & O_NDELAY ) { // remove_wait_queue( & mmvideo_v4l_wait, & wait ); // current->state = TASK_RUNNING; // return -EWOULDBLOCK; // } if ( signal_pending( current ) ) { remove_wait_queue( & mvdev->wq, & wait ); current->state = TASK_RUNNING; return -ERESTARTSYS; } /* sleep */ schedule(); current->state = TASK_INTERRUPTIBLE; down( & mvdev->lock ); } /* post-sleep */ remove_wait_queue( & mvdev->wq, & wait ); current->state = TASK_RUNNING; // printk("mmvideo_v4l_read frame_to_read %d\n", mvdev->frame_to_read ); if ( copy_to_user(buf, mvdev->hw_frame[mvdev->frame_to_read].data, MMVIDEO_FRAMESIZE ) != 0 ) { mvdev->frame_to_read = -1; up( & mvdev->lock ); mvdev->error_cnt ++; return -EFAULT; } mvdev->frame_to_read = -1; up( & mvdev->lock ); mvdev->frame_cnt ++; return MMVIDEO_FRAMESIZE; } static int mmvideo_v4l_mmap( struct video_device * vdev, const char * addr, unsigned long size ) { struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); unsigned long start = (unsigned long)addr; unsigned long page, pos; // printk("mmvideo mmap() size %ld %ld \n", // size, MMVIDEO_NUMFRAMES * MMVIDEO_FRAMESIZE ); if ( size > MMVIDEO_NUMFRAMES * MMVIDEO_FRAMESIZE ) return -EINVAL; down( &mvdev->lock ); pos = (unsigned long) mvdev->fbuf; while ( size > 0 ) { page = kvirt_to_pa( pos ); if ( remap_page_range( start, page, PAGE_SIZE, PAGE_SHARED ) ) { up( &mvdev->lock ); return -EAGAIN; } start += PAGE_SIZE; pos += PAGE_SIZE; if ( size > PAGE_SIZE) { size -= PAGE_SIZE; } else { size = 0; } } up( &mvdev->lock ); return 0; } // this is called by V4L at the end of the initialization // static int mmvideo_v4l_init_done( struct video_device * vdev ) { struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); #if defined( CONFIG_PROC_FS ) && defined( CONFIG_VIDEO_PROC_FS ) if (mmvideo_proc_create_entry( mvdev ) < 0 ) return -1; #endif mvdev->hw_frame_nr = 0; // not really necessary return 0; } static int mmvideo_v4l_ioctl( struct video_device * vdev, unsigned int cmd, void * arg ) { struct mmvideo_device * mvdev = (struct mmvideo_device *)(vdev->priv); switch ( cmd ) { case VIDIOCGCAP: { struct video_capability b; strcpy( b.name, "mmvideo" ); b.type = VID_TYPE_CAPTURE; b.channels = 1; b.audios = 0; b.maxwidth = mvdev->cwidth; b.maxheight = mvdev->cheight; b.minwidth = mvdev->cwidth; b.minheight = mvdev->cheight; if ( copy_to_user( arg, &b, sizeof(b) ) ) return -EFAULT; return 0; } case VIDIOCGCHAN: { // only channel 0 allowed struct video_channel c; if ( copy_from_user( &c, arg, sizeof(c) ) ) return -EFAULT; if ( c.channel != 0 ) return -EINVAL; c.flags = 0; c.tuners = 0; c.type = VIDEO_TYPE_CAMERA; strcpy( c.name, "mmvideo" ); if ( copy_to_user( arg, &c, sizeof(c) ) ) return -EFAULT; return 0; } case VIDIOCSCHAN: return -EINVAL; case VIDIOCGPICT: if ( copy_to_user( arg, &(mvdev->vpict), sizeof(struct video_picture) ) ) return -EFAULT; return 0; case VIDIOCSPICT: return -EINVAL; case VIDIOCGWIN: { // instantiate and fill a video_window struct // // struct video_window w; // w.x = 0; // position on the framebuffer // w.y = 0; // w.chromakey = 0; // w.flags = 0; // w.clipcount = 0; // number of clip rectangles // w.width = mvdev->vwidth; // w.height = mvdev->vheight; // // and copy to userspace // if ( copy_to_user( arg, &w, sizeof(w) ) ) return -EFAULT; // return 0; return -EINVAL; } case VIDIOCSWIN: return -EINVAL; case VIDIOCGMBUF: { struct video_mbuf m; int i; memset(&m, 0, sizeof(m)); m.size = MMVIDEO_FRAMESIZE * MMVIDEO_NUMFRAMES; m.frames = MMVIDEO_NUMFRAMES; for (i=0; ivwidth ) return -EINVAL; if ( m.height != mvdev->vheight ) return -EINVAL; if ( m.frame < 0 || m.frame >= MMVIDEO_NUMFRAMES ) return -EINVAL; // start mmap streaming return mmvideo_hw_mcapture( mvdev, m.frame ); return 0; } case VIDIOCSYNC: { int frame; if ( copy_from_user( (void *)&frame, arg, sizeof(int) ) ) return -EFAULT; if ( frame < 0 || frame >= MMVIDEO_NUMFRAMES ) return -EINVAL; return mmvideo_hw_mrelease( mvdev, frame ); } case VIDIOCGFBUF: { struct video_buffer b; memset( &b, 0, sizeof(b) ); b.base = mvdev->fbuf; b.width = mvdev->cwidth; b.height = mvdev->cheight; b.depth = MMVIDEO_DEPTH; b.bytesperline = mvdev->cwidth * MMVIDEO_DEPTH; if ( copy_to_user( arg, &b, sizeof(b) ) ) return -EFAULT; return 0; } case VIDIOCKEY: return 0; case VIDIOCCAPTURE: // overlay capture not supported return -EINVAL; case VIDIOCGTUNER: case VIDIOCSTUNER: case VIDIOCGFREQ: case VIDIOCSFREQ: case VIDIOCGAUDIO: case VIDIOCSAUDIO: return -EINVAL; default: return -ENOIOCTLCMD; } return 0; } static struct video_device vdev_template = { #ifdef VIDEO_DEVICE_HAVE_OWNER owner: THIS_MODULE, #endif name: "mmvideo", type: VID_TYPE_CAPTURE, // VID_TYPE_OVERLAY not supported // thus no VID_TYPE_CLIPPING / CHROMAKEY // VID_TYPE_FRAMERAM not supported hardware: VID_HARDWARE_PSEUDO, // see videodev.h open: mmvideo_v4l_open, close: mmvideo_v4l_close, read: mmvideo_v4l_read, write: mmvideo_v4l_write, ioctl: mmvideo_v4l_ioctl, mmap: mmvideo_v4l_mmap, initialize: mmvideo_v4l_init_done, // minor: 1, }; /* ------------------------------------------------------------ */ static struct mmvideo_device * mmvideo = NULL; static int __init mmvideo_init( void ) { int i,j; unsigned char * chp; int nr; if ((mmvideo = kmalloc( sizeof(struct mmvideo_device), GFP_KERNEL)) == NULL) { return -1; } memset( mmvideo, 0, sizeof(struct mmvideo_device) ); // video buffer memory allocation mmvideo->fbuf = rvmalloc( MMVIDEO_FRAMESIZE * MMVIDEO_NUMFRAMES ); if ( ! mmvideo->fbuf ) { kfree( mmvideo ); return -1; } mmvideo->hw_fbuf = rvmalloc( MMVIDEO_FRAMESIZE * 2); if ( ! mmvideo->hw_fbuf ) { rvfree( mmvideo->fbuf, MMVIDEO_FRAMESIZE * MMVIDEO_NUMFRAMES ); kfree( mmvideo ); return -1; } // mmvideo->frame_index = 0; // first frame for (i=0; i<2; i++) mmvideo->hw_frame[i].data = mmvideo->hw_fbuf + i * MMVIDEO_FRAMESIZE; for (i=0; iframe[i].data = mmvideo->fbuf + i * MMVIDEO_FRAMESIZE; // now fake an initial picture chp = mmvideo->hw_frame[0].data; for (j=0; j100 && (i+j)<150 && (i-j)>0 && (i-j)<50) *(chp) = 128; else if (i>150 && i<200 && j>100 && j<150) *(chp+1) = 128; else if (i>50 && i<100 && j>150 && j<200 && (i-j)<-100) *(chp+2) = 128; chp += 3; } } mmvideo->hw_frame_nr = 0; memcpy( &mmvideo->vdev, &vdev_template, sizeof(struct video_device) ); mmvideo->vdev.priv = mmvideo; // use private data init_waitqueue_head( &mmvideo->wq ); init_MUTEX( &mmvideo->lock ); wmb(); // the proc dir must be created before registering with V4L #if defined( CONFIG_PROC_FS ) && defined( CONFIG_VIDEO_PROC_FS ) if ( mmvideo_proc_create_dir() < 0 ) { video_unregister_device( &mmvideo->vdev ); rvfree( mmvideo->hw_fbuf, MMVIDEO_NUMFRAMES*2 ); rvfree( mmvideo->fbuf, MMVIDEO_NUMFRAMES*MMVIDEO_FRAMESIZE ); kfree( mmvideo ); return -1; } #endif PDEBUG( "mmvideo_init() registering with video\n"); // nr >= 0 specifies the device // nr = -1 means the first free device if ( // (nr = video_register_device( &mmvideo->vdev, VFL_TYPE_GRABBER, -1)) (nr = video_register_device( &mmvideo->vdev, VFL_TYPE_GRABBER)) == -1) { rvfree( mmvideo->hw_fbuf, MMVIDEO_NUMFRAMES*2 ); rvfree( mmvideo->fbuf, MMVIDEO_NUMFRAMES*MMVIDEO_FRAMESIZE ); kfree( mmvideo ); return -1; } // printk( "mmvideo_init() registered with video, nr. %d\n", nr); return 0; } static void __exit mmvideo_exit( void ) { if ( ! mmvideo ) return; wake_up_interruptible( & mmvideo->wq ); #if defined( CONFIG_PROC_FS ) && defined( CONFIG_VIDEO_PROC_FS ) mmvideo_proc_destroy_dir(); #endif lock_kernel(); if ( mmvideo->user == 0 ) { PDEBUG( "mmvideo_init() unregistering with video\n"); } else { PDEBUG( "mmvideo_init() unregistering when user 1\n"); } video_unregister_device( &mmvideo->vdev ); rvfree( mmvideo->hw_fbuf, MMVIDEO_NUMFRAMES*2 ); rvfree( mmvideo->fbuf, MMVIDEO_NUMFRAMES * MMVIDEO_FRAMESIZE ); kfree( mmvideo ); unlock_kernel(); } module_init( mmvideo_init ); module_exit( mmvideo_exit );