350 likes | 492 Vues
CS 450. Module R5. Next Week. Reminder: R3 & R4 is due next Friday. Only one member of your group needs to be present at demonstration. If your group would like to demonstrate earlier than Friday, please email me by Wednesday afternoon so we can set up a time.
E N D
CS 450 Module R5
Next Week • Reminder: R3 & R4 is due next Friday. • Only one member of your group needs to be present at demonstration. • If your group would like to demonstrate earlier than Friday, please email me by Wednesday afternoon so we can set up a time. • If you are planning on demonstrating in the 813 lab, please let me know of ahead of time. • No documentation due. You do not need to turn in a copy of your source code. Remember to update your manuals accordingly. • Late demonstrations not accepted.
R5 • Due on Friday March 26, 2010 (3 weeks from today) • No Documentation- but you will need to update your Programmer’s manual with the functions you write. They are required to be present when you turn in the final document. • IMPORTANT: Do not use any R5 or R6 related material on Dr. Mooney’s website. My website will have the correct R5 manual and support software. • R5 requires either the use of a serial port emulator, or two computers physically connected via serial ports and a null modem cable (such as the computers in the 851 lab). See the Setup Guide for Com Port Emulator I have posted on my website for setup instructions for the serial port emulator. • R5 is “stand-alone.” It does not depend on the rest of your code, at all! In fact, you will test R5 by compiling it by itself, without the rest of your code. • Do not wait until the last second to do this module. R5 is probably the hardest module in the entire project (as well as the most difficult to debug), so expect to spend a significant amount of time on it. Also, you will need as much time as possible to complete R6, so it is best if you get R5 completed early so you can begin work on R6 when it is assigned.
R5: Introduction • In Module R5, you will be writing an interrupt driven serial port driver which will allow communication with another computer terminal (real or emulated). • You should read the R5 Project Manual to get the background information about device drivers. The manual also has a lot of sample code you can use. • You should also read the chapter 10 text… in particular the section about ring buffers- we will touch on them today but ring buffers are an important element of this module, and you need to have a good understanding of how they work. You will most definitely get questions on ring buffers on the oral exam . • In this module, we will use hexadecimal numbers as port addresses. I strongly recommend you make a symbolic constant for each of the port addresses- this makes debugging and reading your code much easier for you and for me. • I will not help debug your code if you do not use symbolic constants to represent these addresses.
R5: Overview • Functions • com_open: initializes the serial port* • com_close: closes the serial port* • com_read: loads input into the buffer* • com_write: initiates the transfer of data to the serial port* • 3 Interrupt handlers: high level handler which determines the cause of the interrupt and calls a second level interrupt handler to handle the interrupt (you will write two second level interrupt handlers- one for input and one for output) • *For these functions, you must use the exact prototypes provided in the manual. If you do not, the test programs will not work. You may name the interrupt handlers whatever you want. • Data Structures • Device Control Block (DCB): stores information about the device
I/O Interrupts • There are some special concerns with I/O interrupts that aren’t present in the interrupts you programmed in R3. • I/O interrupts can happen at any time (since they are caused by external events). • I/O interrupts must therefore be disabled when they might cause big problems. One of these problems is that the interrupt will affect the stack. • The 8086 architecture has instructions to enable/disable maskable interrupts. You can access these using the Turbo C functions enable() and disable(). • Interrupts should be disabled for the absolute shortest time possible.
Ring Buffers • Input devices often have a complete block of data “buffered” by the device driver before the block is transferred to a process. Character buffering for input devices commonly uses a FIFO buffer- a ring buffer. The buffer is essentially a circular list. • This buffer is used when data is received but no process has requested data. You can think of it as a “read ahead” buffer • Two pointers keep track of the ring buffer. One keeps track of the next place where the interrupt handler can place a newly read character (in), and the other keeps track of where the process should read the next character from (out). • You will implement a ring buffer in your serial port driver. • Read chapter 10 of your textbook for a detailed description of how the ring buffer works, and what control variables are needed and what they are used for.
Event Flags • Event flags are used to indicate the completion of an operation. • In this module, I/O operation will have an associated event flag. Your functions will “set” this event flat by giving it a value of 1. This will indicate that the event (I/O operation) has been serviced and completed. • Once the completion of an I/O operation has been appropriately handled, your functions will “clear” the event flag by giving it a value of 0.
Overview of writing a character string using the driver • A process wishes to write a character array to the serial port. It will call your com_write function and pass to it a buffer containing the string it wishes to send. • Com_write will check that the device is free. If it is, it will indicate that the device is now busy and will write the first byte in the buffer to the serial port • When the byte has been successfully written, an interrupt will be generated. This will trigger your first level interrupt handler. The first level interrupt handler will determine that the cause of the interrupt was a byte being written and will trigger the second level interrupt handler for com_write. • If there are any more bytes left to be written, the second level handler will write the next one. If there are no bytes left to be written, the second level handler will indicate that the I/O operation is completed.
Overview of reading a string using the com_port driver • A process wishes to read a string from the serial port. It will call your com_read function and pass to it a pointer to a buffer (the requestor’s buffer) where the read characters can be placed. It will also pass along the number of characters it wishes to have read. • Com_read will check to see if there is any unread data in the ring buffer (when data is received on the serial port, but no process is requesting data, it is stored in the ring buffer so it can be retrieved later- you can think of the ring buffer as a ‘read ahead’ buffer). If there is data in the ring buffer, it will be transferred to the requestor’s buffer. • If the requestor’s buffer is filled, com_write will indicate that the I/O operation is completed and that the device is free. If the requstor’s buffer is not filled, com_write will leave the device in a state indicated that more data is needed. • At some point, another byte will be written to the serial port, which will trigger your first level interrupt handler. The first level interrupt handler will then call the second level handler for com_read. • The second level handler will check if there is a process still in need of data by checking the device’s status. If the status indicates this, this handler will transfer the byte to the requestor’s buffer. If not, this handler will transfer the byte to the ring buffer so it can be retrieved later. • Once the requestor’s buffer is filled, the second level interrupt handler will indicate the I/O operation is complete.
Dos.h Functions You’ll Need • unsigned char inportp(int port_id); • Reads a byte from the specified I/0 port. Port_id identifies the port. • void outportb (int port_id, unsigned char value) • Writes a byte of data (value) to the specified I/O port. • void enable(void); • Enables all maskable interrupts. • void disable(void) • Disables all maskable interrupts. • void interrupt (*getvect(int int_ID))(void); • Gets the value presently stored in an interrupt vector. This should be the address of the current handler for that interrupt. • void setvect(int int_ID, void interrupt(*handler)(void)); • Sets a specific interrupt cevtore to point to a designated handler.
Device Registers You’ll Need • Programmable Interrupt Controller (PIC) • I/O interrupts are sent here, instead of to the CPU • Has up to 8 input wires, instead of just the one on the CPU • PIC Mask Register • Control/status register that contains a bit for each of the PIC inputs • 0 represents an enabled interrupt • 1 represents a disabled (or masked) interrupt • Access the PIC Mask Register using port number 21h (0x21) • We will need to modify a particular bit without changing the others. • Look in the Project Manual for sample code to show you how to do this. • PIC Command Register • Located at port 0x20. • You will need to send the end-of-interrupt (EOI) code to this register after each PIC Interrupt. The value of this code is: 0x20. • The purpose of this is to tell the PIC to turn off the current interrupt.
Device Registers You’ll Need • Asynchronous Communications Controller (ACC) • Input register (8 bit) – where a byte received on the serial port are placed • Output register (8 bit) – where you place a byte that you want to write to the serial port • Baud rate divisor (16 bit) separated into two parts: MSB (8 bit) & LSB (8 bit) – used to compute the speed of the transfer • Interrupt Enable (8 bit) - • Line Control (8 bit) – parity and number of data bits • Modem Control (8 bit) • Interrupt ID (8 bit) – what interrupt has occurred • Line Status (8 bit) – ready status of the port for input and output • Modem Status (8 bit) – status values
Data Structure:Device Control Block(DCB) • DCB’s store information about the device. Your text book has a more in depth discussion of DCB’s. Information you must put in your DCB is: • Flag indicating whether the device is open or closed • Pointer to the event flag associated with the device. Should be of type (int *). • Status code- possible values are IDLE, READING, WRITING. • Addresses and counters associated with the requestor’s input buffer. For example: • char* in_buff; - pointer the requestor’s buffer (where read data is will be placed) • int *in_count; - counter of the maximum number of chars that can be placed in the buffer • int in_done; -counter for how many characters have been placed in the requestor’s buffer • Addresses and counters associated with the requestor’s output buffer • Same as above, but separate variables needed for output • An array to be used as the ring buffer, with the associated input index, output index, and counter. • char[size] ring_buffer; • int ring_buffer_in; -index where you will write the next character to the ring buffer • int ring_buffer_out; -index where you will remove the next character from the buffer • int ring_buffer_count; -count of how many character stored but not read from the buffer
Com_open int com_open(int *eflag_p, int baud_rate); • Parameters • eflag_p: pointer to an integer event flag within the calling program • baud_rate: integer value representing the desired baud rate • Returns (you may want to define symbolic constants for these return values) • 0 if no error • -101 if invalid even flag pointer • -102 if invalid baud rate divisor • -103 if port already open • Generally, this routine will: • Initialize the DCB • Set the interrupt vector to the new handler address • Computer and store the baud rate divisor • Set other characteristics • Enable all of the necessary interrupts
Com_open outline I • Check parameters • Check that the eflag_p is not null, that baud rate is not negative, and that the device is open • Return appropriate error code if one of these checks fails • Set the device to OPEN • Save the event flag, so your device driver can edit it • Set event_flag = eflag_p • Set the device’s status to IDLE • Initialize the DCB ring buffer variables • Set ringbuffer-head, -tail, -count = 0 • Save the old interrupt handler • Use getvect… see the R5 manual for more information. • Install the new handler • setvect(COM_INT_ID, &com_int); --where com_int is the name of your interrupt handler • Compute the baud rate divisor • See the R5 manual for the equation
Com_open outline II • Store 0x80 in the line control register, this will allow you to change the BRD registers • Set the baud rate divisor in the registers • outportb(COM1_BRD_LSB, brd&0xFF); • outportb(“, (brd >> 8) & 0xFF); • Store 0x03 in the line control register • Enable the appropriate level (for COM1) in the PIC Mask register • Store 0x08 in the modem control register • This will enable serial interrupts • Store 0x01 in the interrupt enable register • This will enable input ready interrupts
Com_close int com_close(void); • Returns • 0 if no error • -201 if serial port not open • Outline • Check that the port is already open (return error code if not) • Close the DCB • Disable the appropriate level in the PIC mask register • Check the manual for code • Loading 0x00 into the Modem Status and Interrupt Enable registers • This will disable serial interrupts • Restore the original interrupt vector • Use setvect- check the manual
Com_read int com_read(char * buf_p, int *count_p); • Parameters • buf_p: far pointer to the starting address of the buffer to receive the input characters • count_p: address of an integer count value indicating the number of characters to be read • Returns • 0 if no error • -301 if port not open • -302 if invalid buffer address • -303 if invalid count address or count value • -304 if device is busy
Com_read- summary Com_readwill initialize the reading of data received on the serial port. It first checks the ring buffer. If it contains data, com_read will copy that data to the requestor’s buffer until the requestor’s buffer is full or the ring buffer is exhausted. If the ring buffer does not contain data (or not enough data to fill the requestor’s buffer), com_read will “reserve” the device by leaving the device’s status at READING.
COM_READ: outline I • Check parameters • Check that the device is OPEN and IDLE, and that buffer and count pointers (parameters) are not null • If any of these checks fail, return an error message • Initialize the input buffer • Save the address of the requestor’s buffer (parameter) to the input buffer in the DCB. This will allow you to copy data into the requestor’s buffer directly from the DCB. You also need to save the address of the count (parameter) to the DCB’s input count variable • Set the input done count to 0 • Clear the event flag- indicates that an operation is beginning • Disable interrupts, set device’s status to READING; now the system will know that a READ operation is under way
COM_READ: outline II • If the ring buffer contains characters that have not yet been read (you can check this by checking the ring buffer count field in the DCB), copy characters from the ring buffer to the requestor’s buffer until either the ring buffer is exhausted, the requestor’s buffer is full, or a ‘\r’ character is encountered (this mean that a return was pressed which indicates string termination) • Re-enable interrupts • If the requestor’s input buffer has not been filled (check this by checking if the input done is less than the input buffer size), return 0. This will keep the device’s status at READING. Since the status is READING, when a character is received later on, your interrupt handler will know to put the character in the requestor’s buffer, not the ring buffer. • If you have not returned from the com_read function by this point, that means that the input buffer is filled and READ operation is over with. You now need to perform some wrap up functions to the read: • You need to null terminate the input buffer (add a \0 to the end of the buffer). • Set status to IDLE and set the event flag to indicate event completion • Return the actual count of characters you placed in the input buffer (this is stored in the input done variable in your DCB) to the input count variable in the DCB… this lets the calling process know how many character you placed in the buffer.
Com_write int com_write(char * buf_p, int *count_p); • Parameters • buf_p: pointer to the starting address of the buffer containing the block of characters to be written • count_p: address of the integer count value indicating the number of characters to be written • Return • 0 if no error • -401 if serial port not open • -402 is invalid buffer address • -403 if invalid count address or count value • -404 if device busy
Com_write: Summary Com_write is called to copy data from a buffer (the requestor’s output buffer- sent as a parameter) to the com_port. The com_write function installs the requestor’s buffer into the DCB, but itself only writes a single character to the com_port. Once that character is written to the device, an interrupt is generated which will trigger your interrupt handler, which will call the second level interrupt handler which will cause another character to be written, which will generate another interrupt, and another character will be written, etc.. until all characters in the buffer have been written.
COM_WRITE: OUTLINE • Check the parameters • Check that the device is open and idle and that the buffer and count parameters are not null. • Return appropriate error code if one of these checks fail • Install the output buffer in the DCB • Save the address of the requestor’s buffer in the DCB’s output buffer pointer, as well as the count. Set the output done count to 0 • Set status to WRITING • Clear the event flag- this indicates that an operation is in progress • Write the first character to the com port • outportb(COM1_BASE, *DCB.outputbuff) • Advance the outputbuffer (outputbuff++) so that when com_write is called again the next character will be written, and increment the outputdone counter to keep track of how many characters you have written. • Enable write interrupts- so when the character you write in the step above actually gets written, the interrupt generated by that event will be seen and captured by the CPU and your interrupt handler will be called. mask = inportb(COM1_INT_EN); mask = mask | 0x02; outportb(COM1_INT_EN,mask); • Return 0
First Level Interrupt Handler- Summary • This interrupt handler will be called when an I/O interrupt on the com port is generated. It will determine the cause of the interrupt (either a read or a write), and will call the appropriate second level interrupt handler
Interrupt Handler: Outline void interrupt name_it(); • Name it whatever you like. • Outline: • If port is not open, clear the interrupt by sending the end of interrupt code to the PIC command register and return. You clear the interrupt to indicate that it has been handled • Otherwise, use inport b to read an int from the Interrupt ID Register to determine the cause of the interrupt. • The value in the interrupt ID register indicates what type of interrupt was generated, either a read or a write. A description of what bits in this value contains the information we need is in the manual. However, one way to find the value is to perform a bit wise and operation (&) between the integer value from the interrupt ID register and 0x07: • If 2, call the write second level interrupt handler • If 4, call the read second level interrupt handler • Clear the interrupt by sending EOI to the PIC command Register
Second Level Interrupt- Read/Input: Summary This interrupt is called when a read interrupt is generated (this means a single character has been placed in the input register). If the current status of the device is READING, this means that a process is currently requesting data from the com port- therefore you will place the character in the requestor’s input buffer. If the status is not READING, no READ operation is currently underway, so you should store the character in the ring buffer.
Second Level Interrupt- Read/Input: Outline void name_it(); • Name is not important… but do not use the “interrupt” keyword! • Outline • Read a character from the input register • If current status is NOT reading, no process is currently requesting data, so we add to the ring buffer • If the ring buffer is full, discard the character and return; • If the ring buffer is not full, store the character in the ring buffer and increment the ring buffer indexes • Put the character in the ring buffer at the index indicated by the “ring buffer in” attribute in the DCB • If current status is reading- a process is currently requesting data so we: • Store the character in the input buffer and increment the count • Check to see if we have finished reading- this happens if the character you just read was a ‘\r’ OR if the input buffer has been filled. If this has occurred: • Null terminate the Input buffer • Set device status to idle • Set the event flag- this indicates that an operation has been completed • Set input count = input done, so the calling function knows how many characters you placed into the buffer
Second Level Interrupt- Write/Output: Summary This interrupt is generated after a character has been successfully written to the com port. It will check if the output buffer in the DCB has been completely copied. If it has, it will perform some cleanup operations and indicate that the operation is complete. If there is still data in the buffer that needs to be copied, it will copy the next character to the output register, which will generate another interrupt, which will cause this procedure to be repeated (and thus the next character written) until all data has been written.
Second Level Interrupt- Write/Output void name_it(); • Name is not important… again do not use the “interrupt” keyword! • Outline • If the current status is NOT writing, Ignore the interrupt and return. • If the requestor’s output buffer has not been completely copied • Copy the next character to the output register and increment the appropriate counts • Return • Otherwise, all characters have been transferred- so we clear the interrupt • Reset the status to idle. • Set the event flag to indicate completion of an operation • Disable write interrupts by clearing bit 1 in the interrupt enable register. mask = inportb(COM1_INT_EN); mask = mask & ~0x02; outportb(COM1_INT_EN ,mask);
Testing and Demonstration • Two test programs are provided on my website: • TESTCOMW.C- tests the serial port output • TESTCOMR.C- tests the output ability but also prompts for and reads input lines. • To execute R5, create a Turbo C project containing your R5.C file, MPX_SUPT.C, and one of the test programs, and compile/run. Make sure to have a terminal connected to COM 2 open- see the setup guide on the website for more info on this. • If you are not using Turbo C, you must include MPX_SUPT.H and link your R5 executable with MPX_SUPT.C and one of the test programs. However, your R5 may work by just including the support header file and not linking- you will have to try this out. However, for this module, you must use the support programs TESTCOMW and TESTCOMR. These are the only acceptable way to test your driver.
Hints • Read the Project Manual and Chapters 9 and 10 in your text. They will give you a much better understanding of these topics as well as some sample source code. • Remember, R5 does not depend, at all, on what you have done in previous modules. R5 is a completely independent portion of the project. If you are compiling/linking any code other than your R5 code and the support software, you are doing it wrong. • Com_open, com_close, com_read, and com_write must have the exact prototypes specified in this power point file and in the project manual in order for the support software to work correctly. However, the first and second level interrupt handlers can be named anything convenient. • Implement com_open and com_close first, then com_write, the first level interrupt handler, and the write interrupt handler. Get your writes to work before you begin to work on reading, which is more complicated. • Do not use printf statements in the interrupt handlers. • Check that your interrupt vectors are being set and cleared properly- this is the most common cause of crashes or hang ups. • Reminder: at the end of the semester there will be an oral exam. You are required to know everything about your project, even parts you do not write. Start looking into things you aren’t clear about now- trying to cram all of the MPX topics in the days before the oral exam will likely result in you getting a bad grade.
Due Date • Remember, Module R5 is due Friday March 26, 2010. That is 3 weeks from today. • I strongly suggest you begin to work on R5 immediately, if at all possible. • There will be no extensions to the due date for this module. If you cannot demonstrate working code on the due date, you will receive no credit. • Even if you fail to complete R5 by the due date, you still need to implement all of its functionality in order to fully test and get credit for R6.