File:  [Apple Darwin 0.x] / driverkit / notes / KernelDMA
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 17:37:50 2018 UTC (8 years, 2 months ago) by root
Branches: MAIN, Apple
CVS tags: HEAD, Darwin03, Darwin02
Darwin 0.2 Driver Kit

Kernel DMA notes


Questions

* Looks like the low-level interrupt code is going to have to be cognizant
  of "is this a DMA interrupt?"....?
  -- Need to grab chan_status? No, that can be done at chan_XX_dequeue time.
     -- no - not valid for m88k!!!
  -- Need to maintain separate 'done queue'? Why? Just scan thru queue of
     kern_dma_desc_t's or kern_dma_frame_t's at chan_XX_dequeue time.
     
  ...so: no way!
  
* Where are DMA overrun and underrun reported???? Referred to at bottom on 
  5-14, can't find the bits.
  
Notes

* use vm_map_check_protection() in vm/vm_map.c to check access.
  pmap_extract() to get physical address.
  pmap_enter() to create mapping for dev_{reg,slot,board}_map().
  
* need another return value from dev_get_type() - 'busy'. When true, the 
  specified dev_number is valid, but is already in use by an internal driver.
  Don't want to return error in this case, it's nice to be able to see what's
  there even if Config can't use it. A dev_port_create on such a busy device
  should return a new error (DR_ATTACHED).
  
* kern_dev_t's - a static array of 8. Need to be able to expand? maybe only at
  config time? No, this is too inflexible. Make a linked list, stash the
  dev_number in the kern_dev_t. Have to map from dev_number to kern_dev very
  seldom (get_dev_type, dev_port_create).
  
  -- besides, don't know size for static array ar config time - need to include
     NextBus boards.
  
Pseuodocode

chan_dma_enqueue() 
{
	check access of region, return if error;
	return error if dma_id is zero;
	check for other errors (channel not attached, etc.);
	check for (stream mode && > 1 page && read) error
	wire the region via vm_map_pageable();
	get a dma_frame_t, init it;
	for each page or less in vm region {
		pmap_extract the physical address;
		if direction == read
			cache_invalidate() the region;
		else
			cache_purge() the region;
		get a dma_descriptor_t;
		set up address, byte_count in the descriptor;
		link to desc_tail;
		desc_tail = this new descriptor;
	}
	if(opts & CEO_EOR)
		set eor in desc_tail's command;
	if(opts & DESC_INTR)
		set desc_intr in desc_tail's command;
	if(opts & CEO_DESC_CMD) 
		write cmd and index to desc_tail; set descCmdValid bit;
	add frame to end of frame_queue;
	enqueue frame's desc_head on current last_frame's desc_tail
		using algorithm defined in DMARaces;
	if(opts & CEO_ENABLE_INTR)
		enable 'em;
}

chan_dma_dequeue()
{
	check for dumb errors (not attached, frame_queue empty, etc.);
	got_a_desc = got_a_frame = FALSE;
	do {
		this_desc = desc_head of head of frame_queue;
		
		if this_desc can be dequeued {
			got_a_desc = TRUE;
			unlink it from frame.desc_head;
			add bytes_xfr for this_desc to frame's bytes_xfr;
			if((this is desc_tail for this frame)  ||
			    EOR || BUFERR) {
				got_a_frame = TRUE;
				write caller's desc_status, eor from this_desc;
			}
			free this_desc;
			if(got_a_frame) {
				/* 
				 * Frame completely dequeued.
				 */
				write caller's dma_id, bcount;
				unwire frame's vm region;
				dequeue frame from frame_queue, free frame;
			}
		}
		else {
			/*
			 * No available descriptor.
			 */
			caller's dma_id = NULL;
		}
	} while(got_a_desc && !got_a_frame);
	return success;
}

Can desc_head of head of frame_queue be dequeued?

if list_not_empty {
	if(desc_head == hw_current_desc)
		desc not complete
	else
		desc complete
}
else {
    	if(desc_head == hw_current_desc) {
		if(count register == -1)
	    		this desc is not complete
		else
	    		this desc is complete
    	}
	else
		this desc is complete
}
else 

 cases:
 1. Channel running. Easy case. Can dequeue this_desc as long as it's not
    hw_current_desc.
 2. Channel not running. 
    a. CDO_ALL: any desc can be dequeued.
    b. CDO_DONE: how to tell what's done? if this_desc == hw_current_desc, 
       is this_desc the last one the TE processed, or the next one (maybe
       it's just been enqueued)?
       
       Can we use the TE count register to see if it's fetched a
       descriptor since we last enqueued...?
		
		eg
		
		channel not running
		chan_dma_enqueue 
			build & enqueue a list (say one descriptor)
			write -1 to count register
		driver enables channel & loads descriptor
		TE writes count register with something other than -1
		DMA complete
		chan_dma_dequeue(CDO_DONE)
		    if channel not running {
			if(desc_head == hw_current_desc) {
			    if(count register == -1)
				this desc is not complete
			    else
				this desc is complete
			}
		    }
		    else
		    	if(desc_head == hw_current_desc)
				desc not complete
			else
				desc complete
		    

Careful - anytime you dequeue a descriptor which is the same as hw_current_desc, have to either NULL out hw_current_desc or write it with desc_head of the next *frame* (NOT the next descriptor in this frame!)

What about dequeueing a whole list of descriptors (in "CDO_DONE mode") and we get to desc == hw_current_desc, and the channel isn't running?
  -- if we have NOT done an enqueue since the channel stopped, hw_count != -1
     ==> this desc (@ hw_curr_desc) is done! (hw_next_desc should be NULL...)
  -- if we HAVE done enqueue since channel stopped, hw_count == -1
     ==> this desc is head of h/w's queue. Not done.
  

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.