⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 dv1394.c

📁 Ieee1394驱动实现
💻 C
📖 第 1 页 / 共 5 页
字号:
/*
 * dv1394.c - DV input/output over IEEE 1394 on OHCI chips
 *   Copyright (C)2001 Daniel Maas <dmaas@dcine.com>
 *     receive by Dan Dennedy <dan@dennedy.org>
 *
 * based on:
 *  video1394.c - video driver for OHCI 1394 boards
 *  Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
  OVERVIEW

  I designed dv1394 as a "pipe" that you can use to shoot DV onto a
  FireWire bus. In transmission mode, dv1394 does the following:

   1. accepts contiguous frames of DV data from user-space, via write()
      or mmap() (see dv1394.h for the complete API)
   2. wraps IEC 61883 packets around the DV data, inserting
      empty synchronization packets as necessary
   3. assigns accurate SYT timestamps to the outgoing packets
   4. shoots them out using the OHCI card's IT DMA engine

   Thanks to Dan Dennedy, we now have a receive mode that does the following:

   1. accepts raw IEC 61883 packets from the OHCI card
   2. re-assembles the DV data payloads into contiguous frames,
      discarding empty packets
   3. sends the DV data to user-space via read() or mmap()
*/

/*
  TODO:

  - tunable frame-drop behavior: either loop last frame, or halt transmission

  - use a scatter/gather buffer for DMA programs (f->descriptor_pool)
    so that we don't rely on allocating 64KB of contiguous kernel memory
    via pci_alloc_consistent()

  DONE:
  - during reception, better handling of dropped frames and continuity errors
  - during reception, prevent DMA from bypassing the irq tasklets
  - reduce irq rate during reception (1/250 packets).
  - add many more internal buffers during reception with scatter/gather dma.
  - add dbc (continuity) checking on receive, increment status.dropped_frames
    if not continuous.
  - restart IT DMA after a bus reset
  - safely obtain and release ISO Tx channels in cooperation with OHCI driver
  - map received DIF blocks to their proper location in DV frame (ensure
    recovery if dropped packet)
  - handle bus resets gracefully (OHCI card seems to take care of this itself(!))
  - do not allow resizing the user_buf once allocated; eliminate nuke_buffer_mappings
  - eliminated #ifdef DV1394_DEBUG_LEVEL by inventing macros debug_printk and irq_printk
  - added wmb() and mb() to places where PCI read/write ordering needs to be enforced
  - set video->id correctly
  - store video_cards in an array indexed by OHCI card ID, rather than a list
  - implement DMA context allocation to cooperate with other users of the OHCI
  - fix all XXX showstoppers
  - disable IR/IT DMA interrupts on shutdown
  - flush pci writes to the card by issuing a read
  - character device dispatching
  - switch over to the new kernel DMA API (pci_map_*()) (* needs testing on platforms with IOMMU!)
  - keep all video_cards in a list (for open() via chardev), set file->private_data = video
  - dv1394_poll should indicate POLLIN when receiving buffers are available
  - add proc fs interface to set cip_n, cip_d, syt_offset, and video signal
  - expose xmit and recv as separate devices (not exclusive)
  - expose NTSC and PAL as separate devices (can be overridden)

*/

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/bitops.h>
#include <asm/byteorder.h>
#include <asm/atomic.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <asm/pgtable.h>
#include <asm/page.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/string.h>
#include <linux/compat.h>
#include <linux/cdev.h>

#include "dv1394.h"
#include "dv1394-private.h"
#include "highlevel.h"
#include "hosts.h"
#include "ieee1394.h"
#include "ieee1394_core.h"
#include "ieee1394_hotplug.h"
#include "ieee1394_types.h"
#include "nodemgr.h"
#include "ohci1394.h"

/* DEBUG LEVELS:
   0 - no debugging messages
   1 - some debugging messages, but none during DMA frame transmission
   2 - lots of messages, including during DMA frame transmission
       (will cause undeflows if your machine is too slow!)
*/

#define DV1394_DEBUG_LEVEL 0

/* for debugging use ONLY: allow more than one open() of the device */
/* #define DV1394_ALLOW_MORE_THAN_ONE_OPEN 1 */

#if DV1394_DEBUG_LEVEL >= 2
#define irq_printk( args... ) printk( args )
#else
#define irq_printk( args... ) do {} while (0)
#endif

#if DV1394_DEBUG_LEVEL >= 1
#define debug_printk( args... ) printk( args)
#else
#define debug_printk( args... ) do {} while (0)
#endif

/* issue a dummy PCI read to force the preceding write
   to be posted to the PCI bus immediately */

static inline void flush_pci_write(struct ti_ohci *ohci)
{
	mb();
	reg_read(ohci, OHCI1394_IsochronousCycleTimer);
}

static void it_tasklet_func(unsigned long data);
static void ir_tasklet_func(unsigned long data);

#ifdef CONFIG_COMPAT
static long dv1394_compat_ioctl(struct file *file, unsigned int cmd,
			       unsigned long arg);
#endif

/* GLOBAL DATA */

/* list of all video_cards */
static LIST_HEAD(dv1394_cards);
static DEFINE_SPINLOCK(dv1394_cards_lock);

/* translate from a struct file* to the corresponding struct video_card* */

static inline struct video_card* file_to_video_card(struct file *file)
{
	return (struct video_card*) file->private_data;
}

/*** FRAME METHODS *********************************************************/

static void frame_reset(struct frame *f)
{
	f->state = FRAME_CLEAR;
	f->done = 0;
	f->n_packets = 0;
	f->frame_begin_timestamp = NULL;
	f->assigned_timestamp = 0;
	f->cip_syt1 = NULL;
	f->cip_syt2 = NULL;
	f->mid_frame_timestamp = NULL;
	f->frame_end_timestamp = NULL;
	f->frame_end_branch = NULL;
}

static struct frame* frame_new(unsigned int frame_num, struct video_card *video)
{
	struct frame *f = kmalloc(sizeof(*f), GFP_KERNEL);
	if (!f)
		return NULL;

	f->video = video;
	f->frame_num = frame_num;

	f->header_pool = pci_alloc_consistent(f->video->ohci->dev, PAGE_SIZE, &f->header_pool_dma);
	if (!f->header_pool) {
		printk(KERN_ERR "dv1394: failed to allocate CIP header pool\n");
		kfree(f);
		return NULL;
	}

	debug_printk("dv1394: frame_new: allocated CIP header pool at virt 0x%08lx (contig) dma 0x%08lx size %ld\n",
		     (unsigned long) f->header_pool, (unsigned long) f->header_pool_dma, PAGE_SIZE);

	f->descriptor_pool_size = MAX_PACKETS * sizeof(struct DMA_descriptor_block);
	/* make it an even # of pages */
	f->descriptor_pool_size += PAGE_SIZE - (f->descriptor_pool_size%PAGE_SIZE);

	f->descriptor_pool = pci_alloc_consistent(f->video->ohci->dev,
						  f->descriptor_pool_size,
						  &f->descriptor_pool_dma);
	if (!f->descriptor_pool) {
		pci_free_consistent(f->video->ohci->dev, PAGE_SIZE, f->header_pool, f->header_pool_dma);
		kfree(f);
		return NULL;
	}

	debug_printk("dv1394: frame_new: allocated DMA program memory at virt 0x%08lx (contig) dma 0x%08lx size %ld\n",
		     (unsigned long) f->descriptor_pool, (unsigned long) f->descriptor_pool_dma, f->descriptor_pool_size);

	f->data = 0;
	frame_reset(f);

	return f;
}

static void frame_delete(struct frame *f)
{
	pci_free_consistent(f->video->ohci->dev, PAGE_SIZE, f->header_pool, f->header_pool_dma);
	pci_free_consistent(f->video->ohci->dev, f->descriptor_pool_size, f->descriptor_pool, f->descriptor_pool_dma);
	kfree(f);
}




/*
   frame_prepare() - build the DMA program for transmitting

   Frame_prepare() must be called OUTSIDE the video->spinlock.
   However, frame_prepare() must still be serialized, so
   it should be called WITH the video->mtx taken.
 */

static void frame_prepare(struct video_card *video, unsigned int this_frame)
{
	struct frame *f = video->frames[this_frame];
	int last_frame;

	struct DMA_descriptor_block *block;
	dma_addr_t block_dma;
	struct CIP_header *cip;
	dma_addr_t cip_dma;

	unsigned int n_descriptors, full_packets, packets_per_frame, payload_size;

	/* these flags denote packets that need special attention */
	int empty_packet, first_packet, last_packet, mid_packet;

	u32 *branch_address, *last_branch_address = NULL;
	unsigned long data_p;
	int first_packet_empty = 0;
	u32 cycleTimer, ct_sec, ct_cyc, ct_off;
	unsigned long irq_flags;

	irq_printk("frame_prepare( %d ) ---------------------\n", this_frame);

	full_packets = 0;



	if (video->pal_or_ntsc == DV1394_PAL)
		packets_per_frame = DV1394_PAL_PACKETS_PER_FRAME;
	else
		packets_per_frame = DV1394_NTSC_PACKETS_PER_FRAME;

	while ( full_packets < packets_per_frame ) {
		empty_packet = first_packet = last_packet = mid_packet = 0;

		data_p = f->data + full_packets * 480;

		/************************************************/
		/* allocate a descriptor block and a CIP header */
		/************************************************/

		/* note: these should NOT cross a page boundary (DMA restriction) */

		if (f->n_packets >= MAX_PACKETS) {
			printk(KERN_ERR "dv1394: FATAL ERROR: max packet count exceeded\n");
			return;
		}

		/* the block surely won't cross a page boundary,
		   since an even number of descriptor_blocks fit on a page */
		block = &(f->descriptor_pool[f->n_packets]);

		/* DMA address of the block = offset of block relative
		    to the kernel base address of the descriptor pool
		    + DMA base address of the descriptor pool */
		block_dma = ((unsigned long) block - (unsigned long) f->descriptor_pool) + f->descriptor_pool_dma;


		/* the whole CIP pool fits on one page, so no worries about boundaries */
		if ( ((unsigned long) &(f->header_pool[f->n_packets]) - (unsigned long) f->header_pool)
		    > PAGE_SIZE) {
			printk(KERN_ERR "dv1394: FATAL ERROR: no room to allocate CIP header\n");
			return;
		}

		cip = &(f->header_pool[f->n_packets]);

		/* DMA address of the CIP header = offset of cip
		   relative to kernel base address of the header pool
		   + DMA base address of the header pool */
		cip_dma = (unsigned long) cip % PAGE_SIZE + f->header_pool_dma;

		/* is this an empty packet? */

		if (video->cip_accum > (video->cip_d - video->cip_n)) {
			empty_packet = 1;
			payload_size = 8;
			video->cip_accum -= (video->cip_d - video->cip_n);
		} else {
			payload_size = 488;
			video->cip_accum += video->cip_n;
		}

		/* there are three important packets each frame:

		   the first packet in the frame - we ask the card to record the timestamp when
		                                   this packet is actually sent, so we can monitor
						   how accurate our timestamps are. Also, the first
						   packet serves as a semaphore to let us know that
						   it's OK to free the *previous* frame's DMA buffer

		   the last packet in the frame -  this packet is used to detect buffer underflows.
		                                   if this is the last ready frame, the last DMA block
						   will have a branch back to the beginning of the frame
						   (so that the card will re-send the frame on underflow).
						   if this branch gets taken, we know that at least one
						   frame has been dropped. When the next frame is ready,
						   the branch is pointed to its first packet, and the
						   semaphore is disabled.

		   a "mid" packet slightly before the end of the frame - this packet should trigger
		                   an interrupt so we can go and assign a timestamp to the first packet
				   in the next frame. We don't use the very last packet in the frame
				   for this purpose, because that would leave very little time to set
				   the timestamp before DMA starts on the next frame.
		*/

		if (f->n_packets == 0) {
			first_packet = 1;
		} else if ( full_packets == (packets_per_frame-1) ) {
			last_packet = 1;
		} else if (f->n_packets == packets_per_frame) {
			mid_packet = 1;
		}


		/********************/
		/* setup CIP header */
		/********************/

		/* the timestamp will be written later from the
		   mid-frame interrupt handler. For now we just
		   store the address of the CIP header(s) that
		   need a timestamp. */

		/* first packet in the frame needs a timestamp */
		if (first_packet) {
			f->cip_syt1 = cip;
			if (empty_packet)
				first_packet_empty = 1;

		} else if (first_packet_empty && (f->n_packets == 1) ) {
			/* if the first packet was empty, the second
			   packet's CIP header also needs a timestamp */
			f->cip_syt2 = cip;
		}

		fill_cip_header(cip,
				/* the node ID number of the OHCI card */
				reg_read(video->ohci, OHCI1394_NodeID) & 0x3F,
				video->continuity_counter,
				video->pal_or_ntsc,
				0xFFFF /* the timestamp is filled in later */);

		/* advance counter, only for full packets */
		if ( ! empty_packet )
			video->continuity_counter++;

		/******************************/
		/* setup DMA descriptor block */
		/******************************/

		/* first descriptor - OUTPUT_MORE_IMMEDIATE, for the controller's IT header */
		fill_output_more_immediate( &(block->u.out.omi), 1, video->channel, 0, payload_size);

		if (empty_packet) {
			/* second descriptor - OUTPUT_LAST for CIP header */
			fill_output_last( &(block->u.out.u.empty.ol),

					  /* want completion status on all interesting packets */
					  (first_packet || mid_packet || last_packet) ? 1 : 0,

					  /* want interrupts on all interesting packets */
					  (first_packet || mid_packet || last_packet) ? 1 : 0,

					  sizeof(struct CIP_header), /* data size */
					  cip_dma);

			if (first_packet)
				f->frame_begin_timestamp = &(block->u.out.u.empty.ol.q[3]);
			else if (mid_packet)
				f->mid_frame_timestamp = &(block->u.out.u.empty.ol.q[3]);
			else if (last_packet) {
				f->frame_end_timestamp = &(block->u.out.u.empty.ol.q[3]);
				f->frame_end_branch = &(block->u.out.u.empty.ol.q[2]);
			}

			branch_address = &(block->u.out.u.empty.ol.q[2]);
			n_descriptors = 3;
			if (first_packet)
				f->first_n_descriptors = n_descriptors;

		} else { /* full packet */

			/* second descriptor - OUTPUT_MORE for CIP header */
			fill_output_more( &(block->u.out.u.full.om),

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -