The Note/1 Driver Design

Similar documents
Character Device Drivers One Module - Multiple Devices

Unit 2 : Computer and Operating System Structure

What is a Linux Device Driver? Kevin Dankwardt, Ph.D. VP Technology Open Source Careers

CS 385 Test 1. 1 Apr 2004

Operating Systems Design Fall 2010 Exam 1 Review. Paul Krzyzanowski

Concurrency and Race Conditions (Linux Device Drivers, 3rd Edition (

PROCESS CONTROL BLOCK TWO-STATE MODEL (CONT D)

Introduction to Operating Systems. Device Drivers. John Franco. Dept. of Electrical Engineering and Computing Systems University of Cincinnati

20-EECE-4029 Operating Systems Fall, 2015 John Franco

Chapter 6: Synchronization. Chapter 6: Synchronization. 6.1 Background. Part Three - Process Coordination. Consumer. Producer. 6.

Chapter 5: Process Synchronization. Operating System Concepts 9 th Edition

Chapter 6: Process Synchronization

Windows Device Driver and API Reference Manual

CROWDMARK. Examination Midterm. Spring 2017 CS 350. Closed Book. Page 1 of 30. University of Waterloo CS350 Midterm Examination.

Linux DRM Developer s Guide

Operating Systems. Designed and Presented by Dr. Ayman Elshenawy Elsefy

Background. The Critical-Section Problem Synchronisation Hardware Inefficient Spinning Semaphores Semaphore Examples Scheduling.

Problem Set 2. CS347: Operating Systems

LOCKDEP, AN INSIDE OUT PERSPECTIVE. Nahim El

Chapter 12 IoT Projects Case Studies. Lesson-01: Introduction

Project No. 2: Process Scheduling in Linux Submission due: April 12, 2013, 11:59pm

More on Synchronization and Deadlock

Lesson 6: Process Synchronization

Computer Organization ECE514. Chapter 5 Input/Output (9hrs)

Chapter 5: Process Synchronization. Operating System Concepts Essentials 2 nd Edition

PROCESS STATES AND TRANSITIONS:

PROCESS SYNCHRONIZATION

Threads Tuesday, September 28, :37 AM

Review: Program Execution. Memory program code program data program stack containing procedure activation records

What is the Race Condition? And what is its solution? What is a critical section? And what is the critical section problem?

- Knowledge of basic computer architecture and organization, ECE 445

CHAPTER 6: PROCESS SYNCHRONIZATION

Chapter 5: Process Synchronization. Operating System Concepts 9 th Edition

rtnl mutex, the network stack big kernel lock

I/O Management Software. Chapter 5

EECE.4810/EECE.5730: Operating Systems Spring 2017 Homework 2 Solution

Templates what and why? Beware copying classes! Templates. A simple example:

SYNCHRONIZATION M O D E R N O P E R A T I N G S Y S T E M S R E A D 2. 3 E X C E P T A N D S P R I N G 2018

Outline. OS Interface to Devices. System Input/Output. CSCI 4061 Introduction to Operating Systems. System I/O and Files. Instructor: Abhishek Chandra

Chapter 6: Process Synchronization

CS370 Operating Systems

CS 378 (Spring 2003)

Chapter 6: Synchronization. Operating System Concepts 8 th Edition,

Using kgdb and the kgdb Internals

3/7/2018. Sometimes, Knowing Which Thing is Enough. ECE 220: Computer Systems & Programming. Often Want to Group Data Together Conceptually

William Stallings Computer Organization and Architecture. Chapter 11 CPU Structure and Function

Multicore and Multiprocessor Systems: Part I

CMSC 412 Project #3 Threads & Synchronization Due March 17 th, 2017, at 5:00pm

Computer Systems Assignment 2: Fork and Threads Package

Jump Tables A jump table is essentially a list of places in the code to jump to. This can be thought of in C as an array of function pointers. In Asse

UNIT- 5. Chapter 12 Processor Structure and Function

Semaphores. Jinkyu Jeong Computer Systems Laboratory Sungkyunkwan University

Orbix TS Thread Library Reference

Parallel Programming Languages COMP360

Chapter 6 Process Synchronization

Interprocess Communication By: Kaushik Vaghani

Operating System Design Issues. I/O Management

University of Waterloo Midterm Examination Model Solution CS350 Operating Systems

Laboratory Assignment #3. Extending scull, a char pseudo-device

I/O Management Software. Chapter 5

The UtePC/Yalnix Memory System

Processes. Operating System CS 217. Supports virtual machines. Provides services: User Process. User Process. OS Kernel. Hardware

Process Synchronization - I

/* $Id: HGSMIBase.cpp $ */ * VirtualBox Video driver, common code - HGSMI initialisation and helper * functions. */

Semaphores. Chapter. verview inary Semaphores for Task Synchronization utex Semaphores to Solve Mutual Exclusion Problems

Background. Old Producer Process Code. Improving the Bounded Buffer. Old Consumer Process Code

ArdOS The Arduino Operating System Reference Guide Contents

Operating Systems 2010/2011

Operating Systems. Lecture 4 - Concurrency and Synchronization. Master of Computer Science PUF - Hồ Chí Minh 2016/2017

CSCE Operating Systems Interrupts, Exceptions, and Signals. Qiang Zeng, Ph.D. Fall 2018

CS370 Operating Systems

Dr. Rafiq Zakaria Campus. Maulana Azad College of Arts, Science & Commerce, Aurangabad. Department of Computer Science. Academic Year

6 System Processes Keyboard Command Decoder CRT Display... 26

CS 167 Final Exam Solutions

CSC Operating Systems Spring Lecture - XII Midterm Review. Tevfik Ko!ar. Louisiana State University. March 4 th, 2008.

CS 322 Operating Systems Practice Midterm Questions

CAN Module Documentation

System Call. Preview. System Call. System Call. System Call 9/7/2018

Processes. Johan Montelius KTH

Pebbles Kernel Specification September 26, 2004

Module 1. Introduction:

Interrupts, Etc. Operating Systems In Depth V 1 Copyright 2018 Thomas W. Doeppner. All rights reserved.

University of Waterloo CS350 Midterm Examination Model Solution

A process. the stack

3.1 Introduction. Computers perform operations concurrently

CPSC/ECE 3220 Fall 2017 Exam Give the definition (note: not the roles) for an operating system as stated in the textbook. (2 pts.

Tolerating Malicious Drivers in Linux. Silas Boyd-Wickizer and Nickolai Zeldovich

Noorul Islam College Of Engineering, Kumaracoil MCA Degree Model Examination (October 2007) 5 th Semester MC1642 UNIX Internals 2 mark Questions

LatticeMico32 GPIO. Version. Features

RAS Enhancement Activities for Mission-Critical Linux Systems

Introduction to OS Synchronization MOS 2.3

CS 167 Final Exam Solutions

An Almost Non- Blocking Stack

Quadros. RTXC Kernel Services Reference, Volume 1. Levels, Threads, Exceptions, Pipes, Event Sources, Counters, and Alarms. Systems Inc.

Review: Program Execution. Memory program code program data program stack containing procedure activation records

Exam TI2720-C/TI2725-C Embedded Software

IV. Process Synchronisation

Chapter 12. CPU Structure and Function. Yonsei University

Recap: Thread. What is it? What does it need (thread private)? What for? How to implement? Independent flow of control. Stack

Implementing Dynamic Minimal-prefix Tries

Transcription:

Nathan Lay Kelly Hirai 6/14/06 Device Drivers The Note/1 Driver Design Introduction The Music Quest Note/1 midi engine is a small midi controller that communicates over the parallel port. It features two midi ports, one for input and one for output. It also features 16 midi-enabled filters and the ability to share the port with a printer. The actual hardware is a black box, it has an Intel PROM chip that holds the mysterious workings of the device itself. What is known is that the device has a 32 byte buffer and appears stateless. The device itself supports reads and writes and is possibly full duplex. Writes are done byte-wise over the data register while reads are done 2 bits at a time over the status register. The status register uses 4 bits: 0x08 - Data bit 1 0x20 - Data bit 0 0x40 - Data RX 0x80 - Data TX The control register bit meanings are still not fully understood but only the 4 lower bits are ever used. Additionally, the device can deliver interrupts but those appear to only happen on read. Likewise, there is a certain discipline manipulating these registers. Usually a write to control and/or data takes place, then the status is examined. However, there appear to be exceptions to this rule where strange bytes are flashed over control and data registers. The Development Method The driver was written in revisions. There were only 4 revisions when this paper was written. The first revision was written for a rough idea of how data and functions were to be laid out while the second revision was a complete re-write, it was intended to be a skeleton for future revisions. The third and fourth revisions were improvements over the second and third revisions respectively, these featured layers of API not present in the previous. These include parport, character device, and ALSA abstraction. This is a summary of all the work done in each revision: Revision 1: - Basic module, implemented parport Revision 2: - Rewrite - Implemented parport - Implemented character device abstraction for parent and child devices. - Added a single ioctl command for probing. - Wrote note1ctl that commands a parent node to probe for newly attached Note/1 devices.

Revision 3: - Added /proc entry that displays device information for each attached Note/1 device. - Removed character device support for child nodes, this wasn't needed. - Added ALSA support. Revision 4: - Changed locking methods for parport. - Improved read/write ALSA methods. The Design Initially one of the major design concerns was the ability to stack on the existing parport driver. Along with this concern was the highly unlikely event that somebody might have more than one Note/1 device attached to the system. Luckily, the parport documentation guarantees us that there will be no more than PARPORT_MAX parallel ports supported. For the PC, PARPORT_MAX defaults to 16. So, in light of this information, the driver holds an array of note1_device pointers set to the size of PARPORT_MAX. On initialization this array is NULL'd out. struct note1_device { struct semaphore sem; /* For locking */ struct pardevice* par; /* Pointer to our port */ struct snd_rawmidi* rmidi; /* ALSA Raw Midi */ struct snd_card* card; /* Our Sound Card */ int busy, claimed, opened; /* parport preempt lock */ ; static struct note1_device* note1_devs[ PARPORT_MAX ]; This array is primarily used for lookup and cleanup as it is used to manage our memory. But before we can make any use of this, the driver must be registered for parport. struct parport_driver { const char *name; void (*attach) (struct parport *); void (*detach) (struct parport *); struct parport_driver *next; ; int parport_register_driver (struct parport_driver *driver); The parport_register_driver is used to make the parport driver aware of our driver as well as provide access to the parallel port. The attach and detach functions our ways to make our driver aware of the parallel ports available and the attach function is triggered for all available ports as soon as the driver is registered. Likewise the detach function is called when a parallel port is no longer available to the system or when parport_unregister_driver is called. The attach function is a perfect place to probe for devices, in fact that's exactly what happens. Our real memory management happens in the attach function. First, note1_getadd()

finds an available slot in the note1_devs array. This can include any NULL'd position, or any allocated data structure that is not already associated to a port. The latter can happen if the parallel port vanishes for that structure or if there was no device to begin with. It is of note that no data structure is deallocated as memory is scarce and this prevents fragmentation. Instead, these already allocated device structures sit in note1_devs until they can be useful or until cleanup. To summarize, suppose there are three parallel ports and the Note/1 device is attached on the third port, then when the attach function is first called, the first position in note1_devs is returned, but since it is NULL, it is allocated and initialized. Then it attempts to probe the the first port and finds nothing. Then when the attach function is called for the second time, it picks the first position in the array again, since it is not associated, likewise this will fail. Lastly it will do the same and associate to the third port. void note1_attach( struct parport* port ) { struct note1_device** p = note1_getaddr(); struct snd_card* card; int res; if (!p ) { printk( KERN_ALERT "%s: Cows can finally fly!\n", if (!*p ) { card = snd_card_new( note1_count, NOTE1_DEVNAME, THIS_MODULE, sizeof( struct note1_device ) ); if ( card == NULL ) { printk( KERN_ALERT "%s: Unable to allocate card memory!\n", ++note1_count; *p = card->private_data; /* Initialize the allocated memory */ memset( *p, 0, sizeof( struct note1_device ) ); init_mutex( &(*p)->sem ); (*p)->card = card; res = snd_rawmidi_new( (*p)->card, NOTE1_DEVNAME, 0, 1, 1, &(*p)->rmidi ); if ( res < 0 ) { printk( KERN_ALERT "%s: Unable to allocate midi memory!\n", snd_card_free( (*p)->card ); *p = NULL; (*p)->rmidi->private_data = *p; strcpy( (*p)->rmidi->name, (*p)->rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT SNDRV_RAWMIDI_INFO_INPUT; snd_rawmidi_set_ops( (*p)->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_note1_input_ops ); snd_rawmidi_set_ops( (*p)->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_note1_output_ops );

if ( snd_card_register( (*p)->card ) ) { printk( KERN_ALERT "%s: Unable to register the card!\n", snd_card_free( (*p)->card ); *p = NULL; if (!note1_register_port( *p, port ) && note1_probe( *p ) ) note1_unregister_port( *p ); else printk( "Found device!\n" ); It is of note that we rely on snd_card_new to allocate our memory for convenience reasons. This, of course was only recent to Revision 3 since ALSA API was added in that revision, before it used kmalloc. One caveat to this memory management technique is that the initialization requires us to register the card, even if there might be no Note/1 devices connected. Registering the card makes our callbacks available to ALSA (they will always fail if there is no device). After initialization, the device attempts to register a port to itself with note1_register_port so it can then attempt to claim the port as well as register parport specific callbacks (such as an interrupt handler). This function only relies on parport_register_device: struct pardevice *parport_register_device(struct parport *port, const char *name, preempt_func preempt, wakeup_func wakeup, irq_func irq, int flags, void *handle); Where preempt is the parallel preemption function, wakeup is a function that reacts on an available port and irq is the interrupt handler. The preempt function will surrender the port to another driver if it is not in use, likewise, the wakeup function will be triggered if a port becomes available. The Note/1 driver only uses preempt and irq since it is able to share the port with a printer and delivers interrupts. If this succeeds, attach is able to call the probing function which determines if the port is to remain associated to the device or not. Put aside until now, the attach function also sets up the appropriate ALSA callbacks on initialization. Since our device is independent of any recognized sound chip, we must use the Raw MIDI API that ALSA provides. The callback functions associated with Raw MIDI include: Input/Output open - Opens a stream Input/Output close - Closes a stream Input/Output trigger - Triggers input and output for a stream and Output drain - Dumps the remaining output buffer to the device. Since our device is byte stream oriented, it does not need a drain function. This is because trigger is guaranteed to take care of that detail for us.

To initialize the driver for use with ALSA, one must initialize a snd_card. A snd_card is a structure that describes a list of sound interfaces through components. One of those components include the Raw MIDI interface. Then one must register a snd_rawmidi structure into the card and associate the snd_rawmidi_ops input and output callbacks into the snd_rawmidi structure. Finally, snd_card_register makes the card available to the system for use, its important that this is done after initializing the snd_card. There are two types of locking needed for this driver. There is the usual kernel lock (e.g. semaphore, mutex, spinlock... ), and to protect the claimed parallel port there is a busy counter. Since there is no real locking issue with concurrency, the big problem lay with port preemption. Initially, the note1_device structure had a mode mask that described the various states of the devices. These states included: opened, busy, and claimed. To protect the previous mode, functions that wanted to change the mode needed to store the current mode. Unfortunately, the problem with this approach became apparent with concurrency. Suppose the device is idle and its opened for input. It stores the current idle mode and sets the mode to busy. Now suppose, at the same time, the device is opened for output, it stores the current busy mode and sets the mode to busy again (which has no effect). Now, when the input method is finished it resets the previous mode it stored, which happens to be the idle mode, despite the fact that the output method intended to be busy too. So, to remedy this issue a busy counter is instead used. Whenever a function intends to be busy, it just increments the busy counter, and when its finished, decrements the counter, promising that the driver's mode is not compromised. To reiterate, this busy counter protects the port from preemption. #define note1_isbusy( dev ) ( (dev)->busy ) #define note1_setbusy( dev ) ( ++(dev)->busy ) #define note1_unsetbusy( dev ) ( --(dev)->busy ) int note1_preempt( void* handle ) { struct note1_device* dev = (struct note1_device*)handle; if ( note1_isbusy( dev ) ) return -1; note1_unsetclaim( dev ); return 0; When the cleanup routine is run, the module unregisters the associated snd_card structures (from the note1_devs array) with snd_card_free. It is of note to mention that this method removes the ALSA callbacks as well as free the memory for our note1_device structure. void exit note1_exit( void ) { struct note1_device** p; #ifdef NOTE1_DEBUG printk( KERN_NOTICE "note1_exit() called!\n" ); #endif parport_unregister_driver( &note1_ppd );

for ( p = note1_devs; p < note1_devs+parport_max; ++p ) if ( *p ) { snd_card_free( (*p)->card ); cdev_del( &note1_cdev ); unregister_chrdev_region( note1_major, NOTE1_DEVS ); remove_proc_entry( NOTE1_DEVNAME, NULL );