Subversion Repositories shark

Rev

Rev 3 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Project: S.Ha.R.K.
 *
 * Coordinators:
 *   Giorgio Buttazzo    <giorgio@sssup.it>
 *   Paolo Gai           <pj@gandalf.sssup.it>
 *
 * Authors     :
 *   Paolo Gai           <pj@gandalf.sssup.it>
 *   Massimiliano Giorgi <massy@gandalf.sssup.it>
 *   Luca Abeni          <luca@gandalf.sssup.it>
 *   (see the web pages for full authors list)
 *
 * ReTiS Lab (Scuola Superiore S.Anna - Pisa - Italy)
 *
 * http://www.sssup.it
 * http://retis.sssup.it
 * http://shark.sssup.it
 */


/**
 ------------
 CVS :        $Id: scom.c,v 1.2 2003-01-07 17:14:05 pj Exp $

 File:        $File$
 Revision:    $Revision: 1.2 $
 Last update: $Date: 2003-01-07 17:14:05 $
 ------------

 Author:      Massimiliano Giorgi

 Author:        Gerardo Lamastra
 Date:  9/5/96

 File:  SCOM.C
 Revision:      1.0g

**/


/*
 * Copyright (C) 2000 Paolo Gai
 *
 * 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
 *
 */


/* Serial communication device */
/* This implementation is capable of handling 4 distinct COM ports */
/* The COM port settings are the standard PC settings:             */
/* PORT         ADDRESS         IRQ                                */
/*   1           0x3F8           4                                 */
/*   2           0x2F8           3                                 */
/*   3           0x3E8           4                                 */
/*   4           0x2E8           2                                 */

/* By Massy
 * I have modified the fast handler routines to support serial
 * mouse better (see below)
 */


//#include <string.h>
//#include <stdlib.h>
//#include <cons.h>

#include <kernel/kern.h>
//#include "exc.h"

#include <modules/sem.h>
#include <drivers/scom.h>


/* #define __DEBUG_SERIAL__ */
/* #define __STUB__ */

/* Base address for each standard COM link */
static unsigned com_base[] = {0x03F8,0x02F8,0x03E8,0x02E8};
/* Irq linked to each serial channel */
static unsigned com_irq[]  = {COM1_IRQ,COM2_IRQ,COM3_IRQ,COM4_IRQ};
/* COM port which shares interrupt with indexed one             */
/* I assume standard AT assignement where each irq line can     */
/* hold up to 2 different COM port                              */
static const int com_share[] = {COM3,COM4,COM1,COM2};

/* Used for decoding the IIR status */
const int IIRbits[] = {MS_CHANGED,TX_EMPTY,RX_FULL,LS_CHANGED};

/* The descriptor of a serial link                               */
/* Each array entry is associated to a COM port                  */
/* The control field is used to specify which kind of interrupts */
/* are going to be served; the status field tracks if the entry  */
/* is curretly used & if the shared fast handler is linked       */
/* The semaphores are opened if you use an asyncronous server    */
/* with the link                                                 */

struct COM_LINK com_link[COM_LINKS];

/* Register level access functions */

unsigned com_read(unsigned port,unsigned reg)
{
    unsigned b;
    if (port > 3 || reg > 7) return(0);
    b = ll_in(com_base[port]+reg);
    return(b);
}

void com_write(unsigned port,unsigned reg,unsigned value)
{
    if (port > 3 || reg > 7) return;
    ll_out(com_base[port]+reg,value);
}

/* Polled send/receive */

void com_send(unsigned port,BYTE b)
{
    while ((com_read(port,LSR) & 32) == 0);
    com_write(port,THR,b);
}

unsigned com_receive(unsigned port)
{
    while ((com_read(port,LSR) & 1) == 0);
    return(com_read(port,RBR));
}

/* Fast routines for cascaded irq */

/* By massy */
/* com_fast must be called if there isn't a server
 * and an interrupt is detected (so sermouse.c module can
 * activate mouse task only if a packet not a byte is received).
 */

static void dummy(int x) {}
void (*com_fast)(int)=dummy;

#ifdef __DEBUG_SERIAL__
    int fast_times1 = 0,fast_times3 = 0;
#endif

static void com_1_3_fast(int n)
{
  unsigned iir1,iir3;
  iir1 = DECODE(com_read(COM1,IIR));
  iir3 = DECODE(com_read(COM3,IIR));
  if ((iir1 & com_link[COM1].control)) {
#ifdef __DEBUG_SERIAL__
    fast_times1++;
#endif
    com_write(COM1,IER,0);
    com_link[COM1].request = iir1;
    if (com_link[COM1].server==NIL)
      com_fast(COM1);
    else
      task_activate(com_link[COM1].server);
  }
  else if ((iir3 & com_link[COM3].control)) {
#ifdef __DEBUG_SERIAL__
    fast_times3++;
#endif
    com_write(COM3,IER,0);
    com_link[COM3].request = iir3;
    if (com_link[COM3].server==NIL)
      com_fast(COM3);
    else
    task_activate(com_link[COM3].server);
  }
}

#ifdef __DEBUG_SERIAL__
    int fast_times2 = 0,fast_times4 = 0;
#endif

static void com_2_4_fast(int n)
{
  unsigned iir2,iir4;
  iir2 = DECODE(com_read(COM2,IIR));
  iir4 = DECODE(com_read(COM4,IIR));
  if ((iir2 & com_link[COM2].control)) {
#ifdef __DEBUG_SERIAL__
    fast_times2++;
#endif
    com_write(COM2,IER,0);
    com_link[COM2].request = iir2;
    if (com_link[COM2].server==NIL)
      com_fast(COM2);
    else    
      task_activate(com_link[COM2].server);
  }
  else if ((iir4 & com_link[COM4].control)) {
#ifdef __DEBUG_SERIAL__
    fast_times4++;
#endif
    com_write(COM4,IER,0);
    com_link[COM4].request = iir4;
    if (com_link[COM4].server==NIL)
      com_fast(COM4);
    else    
      task_activate(com_link[COM4].server);
  }
}

/* Initialize a serial channel */

int com_open(unsigned port,DWORD speed,BYTE parity,BYTE len,BYTE stop)
{
    unsigned long div,b_mask;
       
    /* Check if link is already open */
    cli();
    if (com_link[port].status & LINK_BUSY) {
        sti();
        return(-1);
    } else com_link[port].status |= LINK_BUSY;
    sti();
   
    /* Now set up the serial link */
    b_mask = (parity & 3) * 8 + (stop & 1) * 4 + ((len - 5) & 3);
    div = 115200L / speed;
    /* Clear serial interrupt enable register */
    com_write(port,IER,0);
    /* Empty input buffer */
    com_read(port,RBR);
    /* Activate DLAB bit for speed setting */
    com_write(port,LCR,0x80);
    /* Load baud divisor */
    com_write(port,0,div & 0x00FF);
    div >>= 8;
    com_write(port,1,div & 0x00FF);
    /* Load control word (parity,stop bit,bit len) */
    com_write(port,LCR,b_mask);
    /* Attiva OUT1 & OUT2 */
    com_write(port,MCR,0x0C);
    return(1);
}

/* Link a particular server to a serial channel                 */
/* The semaphores are opened to syncronize the server and the   */
/* application task which use serial communication              */

int com_server(unsigned port,unsigned control,PID server)
{
    unsigned hndl = com_irq[port];
    unsigned shared = com_share[port];
    void (*com_fast)(int n);
    /* Select appropriate fast routine */
    if (port == COM1 || port == COM3) com_fast = com_1_3_fast;
    else if (port == COM2 || port == COM4) com_fast = com_2_4_fast;
    else return(-1);
    if ((control & (RX_FULL|TX_EMPTY)) == 0) return(-1);
    /* If the fast routine is not already installed, install it! */
    cli();
    if (!(com_link[port].status & FAST_INSTALLED)) {
        bit_on(com_link[port].status,FAST_INSTALLED);
        bit_on(com_link[shared].status,FAST_INSTALLED);
        handler_set(hndl,com_fast,NIL);
        #ifdef __DEBUG_SERIAL__
            cputs("Handler OK\n");
        #endif
    }
    sti();
    /* Set com link tasks & flags */
    com_link[port].control = control;
    com_link[port].server = server;
    com_link[port].msk = 0;
    if (control & RX_FULL) sem_init(&com_link[port].rx_sem,0,0);
    if (control & TX_EMPTY) sem_init(&com_link[port].tx_sem,0,0);
    sem_init(&com_link[port].mutex,0,1);
    return(1);
}

/* Close port channel & release the server */

int com_close(unsigned port)
{    
    unsigned hndl = com_irq[port];
    unsigned shared = com_share[port];

    /* Check if fast is already installed */
    cli();
    if (!(com_link[port].status & LINK_BUSY)) {
        sti();
        return(-1);
    } else {
        if (com_link[port].control & RX_FULL) sem_destroy(&com_link[port].rx_sem);
        if (com_link[port].control & TX_EMPTY) sem_destroy(&com_link[port].tx_sem);
        com_write(port,IER,0);
        com_read(port,IIR);
        com_read(port,RBR);
        com_link[port].status = 0;
        com_link[port].control = 0;
        com_link[port].msk = 0;
        sti();
        sem_destroy(&com_link[port].mutex);
        if (com_link[port].server != NIL) {
            task_kill(com_link[port].server);
            com_link[port].server = NIL;
        }
    }
    /* If the fast routine is no more necessary, remove it */
    if (!(com_link[shared].status & FAST_INSTALLED))
       handler_remove(hndl);
    /* If the other link still uses it, we must remember this */
    else com_link[port].status = FAST_INSTALLED;
    return(1);
}

#ifdef __DEBUG_SERIAL__
    int rx_time = 0;
    int tx_time = 0;
#endif

/* This is the full duplex server; used for bidirectional data  */
/* transmission.                                                */
/* As intr is masked, the server can only be activated once     */
/* and operates in mutex with the fast handler                  */
/* This server operates in conjunction with the com_Async...    */
/* procedures!                                                  */

TASK duplexServer(int port)
{
    char data;
    for(;;) {
        if (com_link[port].request & RX_FULL) {
            #ifdef __DEBUG_SERIAL__
                putc_xy(78,0,RED,'R');
                rx_time++;
            #endif
            data = com_read(port,RBR);
            *(com_link[port].rx_buf + com_link[port].rx_cnt) = data;
            com_link[port].rx_cnt++;
            if (com_link[port].rx_cnt == com_link[port].rx_len) {
               bit_off(com_link[port].msk,RX_FULL);
               sem_post(&com_link[port].rx_sem);
            }
        }
        if (com_link[port].request & TX_EMPTY) {
            #ifdef __DEBUG_SERIAL__
                putc_xy(79,0,GREEN,'T');
                tx_time++;
            #endif
            data = *(com_link[port].tx_buf + com_link[port].tx_cnt);
            com_link[port].tx_cnt++;
            com_write(port,THR,data);
            if (com_link[port].tx_cnt == com_link[port].tx_len) {
               bit_off(com_link[port].msk,TX_EMPTY);
               sem_post(&com_link[port].tx_sem);
            }
        }
        cli();
        com_write(port,IER,com_link[port].msk);
        task_endcycle();
        sti();
    }
}

/* This routines provides asyncronous decoupling between the server */
/* and the tasks which produce/consume serial data                  */

void com_AsyncSend(int port,void *buf,unsigned len)
{
    sem_wait(&com_link[port].mutex);
    com_link[port].tx_buf = buf;
    com_link[port].tx_cnt = 0;
    com_link[port].tx_len = len;
    bit_on(com_link[port].msk,TX_EMPTY);
    sem_post(&com_link[port].mutex);
    com_write(port,IER,com_link[port].msk);
    sem_wait(&com_link[port].tx_sem);
}

void com_AsyncReceive(int port,void *buf,unsigned len)
{    
    sem_wait(&com_link[port].mutex);
    com_link[port].rx_buf = buf;
    com_link[port].rx_cnt = 0;
    com_link[port].rx_len = len;    
    bit_on(com_link[port].msk,RX_FULL);
    sem_post(&com_link[port].mutex);
    com_write(port,IER,com_link[port].msk);
    sem_wait(&com_link[port].rx_sem);
}

/* Receive Only Server                                          */
/* This server is used for passive devices which cannot receive */
/* data, but only transmit. I assume that only one byte is pro- */
/* cessed each time and the byte is got to the control process  */

TASK rxServer(int port)
{
    static char data;
    com_link[port].rx_buf = &data;
    for(;;) {
        #ifdef __DEBUG_SERIAL__
            putc_xy(76,0,YELLOW,'R');
        #endif
        data = com_read(port,RBR);
        sem_post(&com_link[port].rx_sem);
        cli();
        com_write(port,IER,com_link[port].msk);
        task_endcycle();
        sti();
    }
}

/* Debug Stub */

#ifdef __STUB__

#include "keyb.h"

TASK Visualize(void)
{
    char str[40];
    char buf[80];
    unsigned long times = 0;

    for (;;) {
        com_AsyncReceive(COM2,str,10);
        sprintf(buf,"Str : %s (Times : %lu)",str,times++);
        puts_xy(0,1,WHITE,buf);
    }
}

TASK Sender(void)
{
    char buf[80];
    unsigned long times = 0;
    cputs("Sender has started...\n");
    for (;;) {
        buf[0] = keyb_getchar();
        if (buf[0] == 'x') task_activate(MAIN_INDEX);
        if ((times % 10) == 9) buf[0] = 0;
        com_AsyncSend(COM2,buf,1);
        sprintf(buf,"Sender (Times : %lu)",times++);
        puts_xy(0,3,WHITE,buf);
    }
}        0

void main()
{
    PID p1,p2,p3;
    BYTE m1 = TX_EMPTY|RX_FULL;
    BYTE m2 = RX_FULL;
    BYTE m3 = TX_EMPTY;
    MODEL m = BASE_MODEL;
    MODEL mm = BASE_MODEL;
   
    sys_init(1000,uSec,0);
    keyb_init(HARD,50);
    clear();
    /* This is the sequence of operations needed to setup */
    /* a serial asyncronous bidirectional link            */
    task_def_arg(m,COM2);
    task_def_wcet(m,500);
    p1 = task_create("ComDuplex",duplexServer,HARD,APERIODIC,100,&m);
    if (p1 == -1) {
        ll_printf("Error creating comduplex task\n");
        sys_end();
        exit(-1);
    }
    com_open(COM2,9600,NONE,8,1);
    com_server(COM2,m1,p1);

    /* Well, that is some other stuff ... */
    task_def_wcet(mm,500);
    p2 = task_create("Visor",Visualize,NRT,APERIODIC,11,&mm);
    p3 = task_create("Sender",Sender,NRT,APERIODIC,11,&mm);
    task_activate(p2);
    task_activate(p3);
    task_endcycle();
    sys_end();
    #ifdef __DEBUG_SERIAL__
        cprintf("RxServer was activated %d times\n",rx_time);
        cprintf("TxServer was activated %d times\n",tx_time);
        cprintf("Fast : %d\n",fast_times1);
    #endif
}

#endif