9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
From: "Eric Smith" <esmithmail@gmail.com>
To: "Fans of the OS Plan 9 from Bell Labs" <9fans@cse.psu.edu>
Subject: Re: [9fans] Writing device drivers
Date: Fri, 14 Apr 2006 10:36:02 -0500	[thread overview]
Message-ID: <820bc1260604140836p4ba0baby9b9ef3d081d2fb51@mail.gmail.com> (raw)
In-Reply-To: <820bc1260604140834v2a773ev90ff46b4e6b2427f@mail.gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 4772 bytes --]

Thank you SO much -- this was just the sort of thing I needed.  Pointing out
the ethernet interface, /sys/src/9/pc/devether.c, and small, easily
understood, sample drivers like etherec2t.c and ether2000.c is just what I
needed.

And you guessed it -- I have spent zero time in kernel mode.  But I'm
anxious to try :)

Use of pointers in C has always been a sticky point for me although it
shouldn't be.  Because I have a background with low-level languages like
assembly I thoroughly understand doing things in memory, registers, etc, but
for some reason, understanding the _why_ of using pointers in C has eluded
me -- like what is to be gained by accessing functions via a table of
pointers.  But that is just what I hope to learn by working on this project.


Truly, though, my C sucks.  I do hope to improve it through this ... I do
hope to succeed.

But to give you an idea of how horrible my C is I've attached a piece of
code I wrote for a (hobby) microcontroller project.  This is for an 8-bit
Atmel RISC.  I know this bears little relation to a device driver living in
kernel space -- and no one would write non-blocking code like this as a
device driver.  But it's probably the best example I have (attached).

Please pardon the heavy comments -- that is the one thing I have learned
about C:  I can go back and look at old, say, Pascal code of mine, and read
it.  But 6 months after writing a little piece of C it's tough for me to
read it and figure out what in the world I was thinking.  So I go overboard
with the comments.

I realize I'm submitting my sorry C code to a group of really strong C
programmers -- if nothing else it should provde some amusement :)

Thank you for your help!!!!

Regards,
Eric


On 4/14/06, Brantley Coile <brantley@coraid.com> wrote:
>
>
> Interesting question.  I just spend a few moments looking around and
> thinking about suggestions for helping you up the learning curve.  The
> more I thought about it, the more I wondered where you were already on
> that curve.  Recommendations depend on that sort of thing.
>
> Suggestions break into three parts: C skills, general device driver
> skills, and Plan 9 specific knowledge.  The C skills fall into three
> categories: the syntax and semantics of the language, common
> techniques and paradigm, and skill in software design, constructing
> solution from small functions and that sort of thing.  The syntax and
> semantics problem can most easily be fixed by a quick re-read of Brian
> and Dennis' book, the famaous K&R.  Most likely you've read that
> before, but I re-read it every few years or so.  Amazing that a
> language so small can take a life time to fully learn.  (And I'm not
> talking about C99.)
>
> Common techniques and paradigms, since you want to write a Plan 9
> device driver, is best learned by reading Plan 9 code.  Lots of nice
> small things in /sys/src/cmd are great for this.  As with natural
> languages, the best way to learn to write is to read.  I owe
> everything to this principle.
>
> The skills in design are much harder to obtain.  You may already be
> strong in this department since it's not unique to C. The two
> attributes of cohesion and coupling are the key ideas here.  Good code
> in Oberon, Java, C, PL/I or COBOL are all the same.
>
> Brian and Rob's book, `The Practice of Programming' is the best
> I know of for this kind of stuff.
>
> I assume from your comment that you time spend writing stuff in kernel
> mode is limited.  Sorry if this isn't the case.  Things you should
> learn include, the restricted context of kernel mode, the issues
> regarding locking in the multiprocessor Plan 9 kernel, how interrupt
> handlers get registered and called, limitations on what you can do in
> the interrupt handler, how processes sleep and wakeup, and more.
> Interfacing to the under part of Plan 9 is easy.  It's just a 9P
> server with function calls instead of messages.  Simple device drivers
> in like /sys/src/9/pc/devrtc.c show how the device driver interfaces
> with the system.
>
> But you want to write a driver for the WPN311, so you'll need to study
> the ethernet interface in /sys/src/9/pc/devether.c, and look at an
> ethernet sample driver like etherec2t.c and ether2000.c .  These are
> pretty small and show how to glue into the devether.c code.
> Devether.c factors out all the common work of ethernet drivers and
> uses a table of pointers to functions to get the specific work done by
> a given driver.
>
> Hope that helps some.  I've never written a wireless driver so I don't
> know about any specific gotchas with that.  Good luck on it.  It's a
> great feeling to get a driver working.
>
> Brantley
>
>
>

[-- Attachment #1.2: Type: text/html, Size: 5682 bytes --]

[-- Attachment #2: acq.c --]
[-- Type: text/x-csrc, Size: 10800 bytes --]

/* vim: set sw=8 ts=8 si :
* Author                : Eric Smith
* Date                  : July 2005
* Chip type             : ATMEGA8
* Clock frequency       : 400 kHz (external ceramic resonator)
* Program		: acq.c
* Number		: 0x0A
* Data Format Version	: 0x1A
*
* This program acquires data from three sensors and stores in EEPROM.
* Data from one of the three sensors (jumper selectable) is displayed on
* PORTD's 8 LED display.  EEPROM writes occur once per write interval
* which can be once per second, minute, five minutes, or hour.  After EEPROM
* is full, or SS_WRITE_ENABLE jumper is unset, writes will cease, however,
* the display will continue to update constantly.
*
* Data Format (ver 0x1A):
*				EEPROM @ 0x000 = Program Number
*				EEPROM @ 0x001 = Data Format Version Number
*				EEPROM @ 0x002 - 0x1FF = 170 3-byte records
*				Record Format (3 bytes):
*						Byte 0 = thermistor
*						Byte 1 = photocell
*						Byte 2 = magnetic
******************************************************************************/
#include <avr/io.h>
#include <inttypes.h>

/* GLOBALS */

// JIFFIES_CONSTANT should be set to the number of times the program
// can pass through the main loop in one second.  This will be the basis for
// setting the write interval (1 per sec, 1 per min, 1 per 5mins, or 1 per hour)
// While this processor is capable of running on a 16 MHz clock we are loping
// along at 400 kHz to save power ... which is ok: there's no rush.
const unsigned long int JIFFIES_CONSTANT = 850;

const unsigned long int led_lit_limit = 85;  // should be JIFFIES_CONSTANT/10

char flags = 0;  // clear all flags

// sense switch: 1=flash PC5 LED on writes, 0=quiet
const int SS_LED_ENABLE =	(1<<0);

// sense switch: 1=allow display on PORTD, 0=quiet
const int SS_DISPLAY_ENABLE =	(1<<1);

// sense switch: 1=write enabled, 0=write disabled
const int SS_WRITE_ENABLE =	(1<<2);

unsigned long int loop_counter;

uint8_t record_number;

unsigned short int PC = 0x000;	// this is the EEPROM address pointer

uint8_t SAMPLE_INTERVAL; /* How often to record data:
				0 = 1 per second
				1 = 1 per minute
				2 = 1 every 5 minutes
				3 = 1 per hour		*/

uint8_t DISPLAY_SELECTION; /* What to display on PORTD LEDs:
				0 = record number
				1 = thermistor
				2 = photocell
				3 = magnetic		*/

struct {
	uint8_t thermistor;
	uint8_t photocell;
	uint8_t magnetic;
} sensor;

/* FUNCTION PROTOTYPES */
void delay_ms(unsigned short);
void eeprom_write_byte(unsigned char, unsigned int);
void init(void);
void read_sense_switches(void);
void acquire_data(void);
void display_data(uint8_t);
void write_data(void);

/* FUNCTIONS */
void delay_ms(unsigned short ms)
// delay for a minimum of <ms>
// with a 1Mhz clock, the resolution is 1 ms
{
        uint8_t i,j;
        while (ms) {
                i = 40;  // 40 for 400 kHz clock, 100 for 1 MHz, etc
                while (i) {
                        i--;
                        j = 70;
                        while (j) {
                                j--;
                        }
                }
                ms--;
        }
}

void eeprom_write_byte(unsigned char data_byte, unsigned int eepromAddr)
// Write a byte of data to eeprom at eepromAddr
{
        while (EECR & (1<<EEWE))  // wait for completion of last write
                ;
        EEAR = eepromAddr;      // Setup address
        EEDR = data_byte;       // and data registers
        EECR |= (1<<EEMWE);     // Write logical 1 to master write enable
        EECR |= (1<<EEWE);      // start EEPROM write
}

void init()  // init routine run once only at start of main()
{
	delay_ms(500);	// let's just wait half a sec to make sure we are ON

	PORTB = (1<<PB2) | (1<<PB1) | (1<<PB0);  // enable PB0..2 as inputs
	DDRB = 0x00;
	PORTC = (1<<PC4) | (1<<PC3);  // enable PC3..4 as inputs
	DDRC = 0x00;
	DDRC |= _BV(PC5);	// enable PC5 as output
	PORTC |= _BV(PC5);	// Set PC5 output to 5V, LED off (active low)
	DDRD = 0xFF;	// enable port D
	PORTD = 0xFF;	// set all bits to one = all LEDs off (active low)
	ADCSR = 0xC2;   /* 11000010, left to right: ADC Enable,
                           Start Conversion, Single Conversion Mode,
                           write zero to ADC Int Flag, disable int,
                           prescaler: 010 for XTAL/4            */
	ADMUX = (1<<REFS0) | (1<<ADLAR); // ADLAR = JUSTIFY
	ADMUX &= ~_BV(MUX3);	// This bit always zero; see acquire_data
/*  ZERO of record_number below is candidate for removal.
	record_number = 0;	// zero record number
*/
	eeprom_write_byte(0x0a, PC++);	// 0A = program identifier
	eeprom_write_byte(0x1a, PC++);	// 1A = data format identifier
}  // PC = 0x002 on exit

void read_sense_switches()
// read jumper settings and store in flags structure
{
	flags |= SS_LED_ENABLE;		// LED flash always enabled for now
	flags |= SS_DISPLAY_ENABLE;	// Display always enabled for now

// DISPLAY_SELECTION:
//	0x00 = display record_number
//	0x01 = display thermistor data
//	0x02 = display photocell data
//	0x03 = display magnetic data
//
//	PINB0 = LSB
//	PINB1 = MSB

	if ((PINB & _BV(PINB1)) == 0)  // if this pin is LO then jumper IS set
		DISPLAY_SELECTION = 2;
	else
		DISPLAY_SELECTION = 0;
	if ((PINB & _BV(PINB0)) == 0)  // if this pin is LO then jumper IS set
		DISPLAY_SELECTION++;

// SAMPLE_INTERVAL:
//	0x00 = write EEPROM once per second
//	0x01 = write EEPROM once per minute
//	0x02 = write EEPROM once every five minutes
//	0x03 = write EEPROM once per hour
//
//	PINB2 = LSB
//	PINC3 = MSB

	if ((PINC & _BV(PINC3)) == 0)  // if this pin is LO then jumper IS set
		SAMPLE_INTERVAL = 2;
	else
		SAMPLE_INTERVAL = 0;
	if ((PINB & _BV(PINB2)) == 0)
		SAMPLE_INTERVAL++;

// SS_WRITE_ENABLE:
//	0x00 = write disabled
//	0x01 = write enabled
//
//	PINC4 = this bit ... AND, as with all, JUMPER IN takes pin LOW

	if ((PINC & _BV(PINC4)) == 0)	// pin LOW means JUMPER is IN, so:
		flags |= SS_WRITE_ENABLE;  // set SS_WRITE_ENABLE bit
	else
		flags &= ~SS_WRITE_ENABLE; // JUMPER OUT, clear SS_WRITE_ENABLE
}

void acquire_data()
// digitize sensor data and store in sensor structure
//	ADC0 = magnetic (Hall effect device, expected ouput 0.5-4.5V, AREF = Vcc
//	ADC1 = thermistor, expected output 0-2.5V, AREF = 2.56V
//	ADC2 = photocell, expected output 0-2.5V, AREF = 2.56V
{
	// Let's do ADC0 (magnetic sensor) first:
	ADMUX &= ~_BV(MUX0);// Next three lines sets MUX to ADC0.  Already set
	ADMUX &= ~_BV(MUX1);	// in init() are LEFT JUSTIFY, VREF=Vcc & REFS0
	ADMUX &= ~_BV(MUX2);

	while (ADCSR & (1<<ADSC))	// wait for last conversion to complete
		;

	ADCSR |= (1<<ADSC);	// start conversion (throw last one away)
	while (ADCSR & (1<<ADSC))	// wait for it to finish
		;
	sensor.magnetic = ADCH;  // get high 8 bits of magnetic data

	// Now let's do ADC2 (photocell):
	ADMUX |= _BV(MUX1);	// MUX bits now 0010 = ADC2
	ADCSR |= (1<<ADSC);	// start conversion, another keeper
	while (ADCSR & (1<<ADSC))	//wait for it to finish
		;
	sensor.photocell = ADCH;  // get high 8 bits of photocell data

	// Now let's do ADC1 (thermistor):
	ADMUX |= _BV(REFS1);	// set 2.56V AREF, REFS0 already set in init()
	ADMUX &= ~_BV(MUX1);
	ADMUX |= _BV(MUX0);	// MUX bits now 001 = ADC1

	ADCSR |= (1<<ADSC);	// start conversion (will throw this one away)
	while (ADCSR & (1<<ADSC))	// wait for it to finish
		;

	ADCSR |= (1<<ADSC);	// start a new conversion (this one's a keeper)
	while (ADCSR & (1<<ADSC))	//wait for it to finish
		;
	sensor.thermistor = ADCH;  // get high 8 bits of thermistor data

	// Now let's set up Vcc AREF and start a conversion to throw away on
	// next time through here
	ADMUX &= ~_BV(REFS1);	// clear REFS1, REFS0 still set, so AREF = Vcc
	ADCSR |= (1<<ADSC);	// start a conversion to throw away on re-entry
}

void display_data(uint8_t selection)
// Display data selected by sense switches if display is enabled and
// turn off PC5 LED if LED lit time is expired -- intent is to turn on
// the LED at write, then off after about 1/10 second or so while
// PORTD LED updates continue to occur even during the 1/10 second
{
	if ((flags & SS_LED_ENABLE) != 0) {		// if LED is enabled and
		if (loop_counter > led_lit_limit)	// lit limit exceeded
			PORTC |= _BV(PC5);		// then turn LED off
	}
	if ((flags & SS_DISPLAY_ENABLE) !=0) {	// if DISPLAY is enabled
		// invert bits and display data on Port D LEDs
		// (must be inverted 'cause LEDs are active low)
		switch (selection) {
			case 0:
				PORTD = ~record_number;
				break;
			case 1:
				PORTD = ~sensor.thermistor;
				break;
			case 2:
				PORTD = ~sensor.photocell;
				break;
			case 3:
				PORTD = ~sensor.magnetic;
				break;
		}
	}
}

void write_data()
// Write data in sensor structure to EEPROM at eepromAddr
// Light PC5 LED if enabled through sense switches (SS_LED_ENABLE)
{
	if ((flags & SS_LED_ENABLE) != 0)	// if LED is enabled
		PORTC &= ~_BV(PC5);		// then turn it ON! (active low)
	eeprom_write_byte(sensor.thermistor, PC++);
	eeprom_write_byte(sensor.photocell, PC++);
	eeprom_write_byte(sensor.magnetic, PC++);
}

int main(void)
{
	init(); // initialization routine to be run once only
	unsigned long int loop_limit = JIFFIES_CONSTANT; // for starters
	uint8_t record_limit = 170;  // 170 records = 2 bytes + (170 *3)
					// first byte is program number
					// second byte is record type
					// record type describes data structure
					// of 170 records of 3 bytes each

	while (1) {  // THIS IS THE MAIN LOOP
		loop_counter = 0;
		while (loop_counter < loop_limit) {
			read_sense_switches();
			switch (SAMPLE_INTERVAL) {
				case 0:		// write ONCE PER SECOND
					loop_limit = JIFFIES_CONSTANT;
					break;
				case 1:		// write ONCE PER MINUTE
					loop_limit = JIFFIES_CONSTANT * 60;
					break;
				case 2:		// write ONCE PER FIVE MINUTES
					loop_limit = JIFFIES_CONSTANT * 300;
					break;
				case 3:		// write ONCE PER HOUR
					loop_limit = JIFFIES_CONSTANT * 3600;
					break;
			}
			acquire_data();
			display_data(DISPLAY_SELECTION);
			loop_counter++;
		}

		// loop_counter has exceeded loop_limit, time to write data
		// BUT, write EEPROM only if record limit not exceeded.
		// (will continue to loop and display even if EEPROM is full)

		if (record_number < record_limit) {
			if ((flags & SS_WRITE_ENABLE) !=0) {
				write_data();
				record_number++;
			}
		}
	}  // close of main loop -- will loop here forever, always displaying
	   // data, and writing to EEPROM only if WRITE_ENABLE is set and
	   // record_limit is not exceeded
}




       reply	other threads:[~2006-04-14 15:36 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <8a5bd8ccbc2e53557663e2a9020fb26c@coraid.com>
     [not found] ` <820bc1260604140834v2a773ev90ff46b4e6b2427f@mail.gmail.com>
2006-04-14 15:36   ` Eric Smith [this message]
2006-04-14 19:33     ` David Leimbach
2006-04-15  4:58     ` jmk
2006-04-15 13:24       ` Eric Smith
2006-04-15 14:16         ` Anthony Sorace
2006-04-16 23:00           ` Paweł Lasek
2006-04-19  3:10 erik quanstrom
2006-04-19  4:02 ` jmk
2006-04-19  4:57   ` lucio
2006-04-19  6:59   ` Nigel Roles
2006-04-19 21:12   ` quanstro
  -- strict thread matches above, loose matches on Subject: below --
2006-04-17  0:31 erik quanstrom
2006-04-17  1:44 ` Russ Cox
2006-04-17 10:01   ` Nigel Roles
2006-04-18  1:34     ` erik quanstrom
2006-04-18 14:49       ` Nigel Roles
2006-04-18 15:42         ` jmk
2006-04-18 15:59         ` Moritz Kiese
2006-04-18 16:03           ` Nigel Roles
2006-04-18 19:15       ` Paweł Lasek
2006-04-17  0:11 erik quanstrom
2006-04-17  1:24 ` Russ Cox
2006-04-16  1:02 erik quanstrom
2006-04-16  2:36 ` Anthony Sorace
2006-04-16  8:06   ` Bruce Ellis
2006-04-15 23:34 erik quanstrom
2006-04-15 23:55 ` Skip Tavakkolian
2006-04-15 15:26 erik quanstrom
2006-04-15 23:04 ` Federico Benavento
2006-04-15 23:18   ` Skip Tavakkolian
2006-04-16  0:43     ` Federico G. Benavento
2006-04-16 18:03     ` Charles Forsyth
2006-04-16 18:14       ` Bruce Ellis
2006-04-16 21:31         ` Charles Forsyth
2006-04-14  3:20 Eric Smith

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=820bc1260604140836p4ba0baby9b9ef3d081d2fb51@mail.gmail.com \
    --to=esmithmail@gmail.com \
    --cc=9fans@cse.psu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).