EnetDrvrSrc.c

/*
    File:       EnetDrvrSrc.c
 
    Contains:   A sample Ethernet driver source sample based on the sample loopback driver
                which is supplied as part of the OT Module Driver SDK.  This source file 
                extends the loopback driver to demonstrate 
                1. the installation of the interrupt handler,
                2. how to access the kAAPLDeviceLogicalAddress property in order to obtain 
                    the base address registers for the PC Card.
                3. how to read the Ethernet burned in address (BIA) from either
                    attribute memory, if not in the CIS, or from the CISTPL_FUNCE if it is
                    in the CIS.
                    
    Original comments:   
                 * Simple loopback driver utilizing Mentat DLPI Template Code (dlpiether.c)
                 * This file, combined with dlpiether.c (or linked against the Shared LIbrary
                 * containing dlpiether.c) is a functioning loopback driver for Open Transport.
                 * This file also serves as a template for writing the hardware-specific
                 * code for a driver which will use dlpiether.c.  Comments in the code and
                 * the accompanying documentation, Mentat DLPI Driver Template, provide
                 * necessary information.
 
    Written by: Mentat Inc. 
 
    Change History (most recent first):
                8/16/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
char    sccs_loop_id[] = "@(#)EnetDrvrSrc.c 1.2";
 
/*
 * "BoardX:" in comments refers to a typical hardware driver.  These are hints
 * and suggestions for converting this code to an actual hardware driver.
 */
#include <OpenTptModule.h>
#include <dlpi.h>
#include "Devices.h"
#include <NameRegistry.h>
#include <DriverServices.h>
#include <Interrupts.h>
#include <OpenTptLinks.h>
#include <OpenTptPCISupport.h>
#include "dlpiuser.h"
#include "dlpiether.h"
#define ulong UInt32
#define uint UInt16
 
#include <PCCard.h>
#include <PCCardTuples.h>
 
// enter the name of the module as registered using the OTRegisterPort call.
// Use the PCCardDisplayNameRegistry utility to find the module name for the
// This name MUST match else TCP/IP services WILL not work.
#define kEnetCardName   "myEnetDriverName"
 
/*
 * The following defines are inserted here for convenience for the 
 * loopback driver.  A "real" driver may place these in separate header
 * files and/or use system-defined values.
 */
//#define LOOPBACK_DRIVER       /* conditionally compiles loopback code */
 
#define ENETADDRINATTRIBUTE     /* conditionally compiles code to obtain the ethernet
                                    burned in address from attribute memory 
                                    comment out this define to had code access BIA
                                    from CIS
                                */
 
/* Some useful MacBug debugging macros */
 
#define mps_printf          OTKernelPrintfForMentat
#define loop_error(a)       mps_printf a
#define loop_trace(a)       mps_printf a
#define loop_debug(a)       if(loop_debug) {mps_printf a;} else {;}
#define loop_debug2(a)      if(loop_debug >= 2) {mps_printf a;} else {;}
#define trace_args  "  "        
#define trace0      "0 "    /* Fatal */
#define trace1      "1 "    /* General */
#define trace2      "2 "    /* Out of Memory */
#define trace3      "3 "    /* Event */
#define trace_eol       
extern  int OTKernelPrintfForMentat(char * fmt, ...);
int loop_debug = 1;
 
#define MAX_PACKET_SIZE 1500    /* We're ethernet, remember :-) */
#define MIN_PACKET_SIZE 60  /* We'll pad short packets Ethernet style */
#define LOOP_HIWAT  2048    /* Flow control high water mark */
#define LOOP_LOWAT  64  /* Flow control low water mark */
 
/* just so DL_INFO_ACK has something to report */
static  unsigned char   my_hardware_addr[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
 
/*
 * See OpnTptLinks.h for more
 */
 
/* Driver information 'per board', e.g., internal control structure. */
typedef struct board_s {
    dle_t   board_dle;      /* Must be first in structure. */
    /* BoardX: Fields needed for controlling hardware go here */
} board_t;
 
/* 
 * STREAMS instance data (q_ptr) 
 */
typedef struct loop_s {
    dcl_t   loop_dcl;       /* Must be first in structure */
    /* BoardX: Any 'per stream' fields needed by board go here */
} loop_t;
#define loop_dle    loop_dcl.dcl_hw     /* pointer to board_t structure */
 
static  void    loop_bcopy_to_noncached(unsigned char * src, unsigned char * dst
            , unsigned int count);
static  int loop_close(queue_t * q, int flag, cred_t * credp);
static  void    loop_hw_address_filter_reset(void * loopvp, dle_addr_t * addr_list
            , ulong addr_count, ulong promisc_count
            , ulong multi_promisc_count, ulong accept_broadcast
            , ulong accept_error);
static  void    loop_hw_start(void * loopvp);
static  void    loop_hw_stop(void * loopvp);
static  int loop_open(queue_t * q, dev_t * devp, int flag, int sflag
            , cred_t * credp);
static  int loop_rsrv(queue_t * q);
static  int loop_wput(queue_t * q, mblk_t * mp);
install_info*   GetOTInstallInfo ();
Boolean InitStreamModule (RegEntryID * our_id);
void board_bcopy_to_noncached (unsigned char * src, unsigned char * dst, unsigned int count);
// DRF start: added for PCCard Kitchen
 
InterruptMemberNumber board_intr(InterruptSetMember ISTmember, void *refCon, UInt32 theIntCount);
void TerminateStreamModule (void);
OTResult ValidateHardware (RegEntryID * our_id);
 
InterruptEnabler    gInterruptEnabler;
InterruptDisabler   gInterruptDisabler;
 
volatile void *     gCardBaseAddress;
unsigned char       gEthernetHardwareAddress[6];
 
// DRF end
 
 
/*
 * Global pointer to the board_t structure for this device instantiation.  This
 * structure is allocated in the InitStreamModule routine and freed in
 * TerminateStreamModule.  CFM guarantees us a separate data space for all
 * instances of the device, so this pointer is always unique.
 */
board_t * board_global;
 
/* STREAMS configuration structures. */
struct module_info info =  {
    kEnetModuleID, kEnetCardName
    , MIN_PACKET_SIZE, MAX_PACKET_SIZE
    , LOOP_HIWAT, LOOP_LOWAT
};
 
/* 
 * STREAMS qinit structures.  Drivers have no rput.
 */
struct qinit rinit = {
    NULL, loop_rsrv, loop_open, loop_close, NULL, &info
};
 
struct qinit winit = {
    loop_wput, NULL, loop_open, loop_close, NULL, &info
};
 
struct streamtab loopback_info = { &rinit, &winit };
 
/* 
 * BoardX:  Hardware drivers would typically add 'kOTModUsesInterrupts' to
 * flags entry of following.
 */
 
install_info    loopback_install_info =
{
    &loopback_info,
    kOTModIsDriver | kOTModUpperIsDLPI,
    SQLVL_MODULE,
    NULL,
    0,
    0
};
 
void board_bcopy_to_noncached (unsigned char * src, unsigned char * dst, unsigned int count)
{
    unsigned int iterations;
 
    /* Do short copies with a simple loop. */
    if (count <= 15) {
        do {
            --count;
            *dst++ = *src++;
        } while (count);
        return;
    }
 
    /* Align on 8 byte dst boundary */
    iterations = (8 - ((unsigned int)dst & 0x7)) & 0x7;
    count -= iterations;
    if (iterations) {
        do {
            --iterations;
            *dst++ = *src++;
        } while (iterations);
    }
 
    /* Copy 32 byte chunks */
    iterations = count >> 5;
    count &= 0x1F;
    if (iterations) {
        double * dsrc = (double *)src;
        double * ddst = (double *)dst;
        do {
            iterations--;
            ddst[0] = dsrc[0];
            ddst[1] = dsrc[1];
            ddst[2] = dsrc[2];
            ddst[3] = dsrc[3];
            ddst += 4;
            dsrc += 4;
        } while (iterations);
        src = (unsigned char *)dsrc;
        dst = (unsigned char *)ddst;
    }
 
    /* Copy 4 byte chunks */
    iterations = count >> 2;
    count &= 0x3;
    if (iterations) {
        unsigned long * lsrc = (unsigned long *)src;
        unsigned long * ldst = (unsigned long *)dst;
        do {
            --iterations;
            *ldst++ = *lsrc++;
        } while (iterations);
        src = (unsigned char *)lsrc;
        dst = (unsigned char *)ldst;
    }
 
    /* Copy the rest */
    while (count--)
        *dst++ = *src++;
}
 
/*
 * Template interrupt routine for processing inbound packets.
 */
 
InterruptMemberNumber board_intr (InterruptSetMember ISTmember, void *refCon, UInt32 theIntCount)
{
    #pragma unused(ISTmember,refCon,theIntCount)
    board_t * board = board_global;
    dle_t   * dle = &board->board_dle;
 
    OTEnterInterrupt();
 
    loop_debug((trace_args "entered" trace_eol));
 
    OTLeaveInterrupt();
    return 1;
}
 
 
/* This function must be exported; see loop.exp */
install_info *
GetOTInstallInfo ()
{
    DebugStr("\p entered GetOTInstallInfo ");
    loop_debug((trace_args "GetOTInstallInfo" trace_eol));
    return &loopback_install_info;
}
 
Boolean
InitStreamModule (RegEntryID * our_id)
{
    board_t * board;
    dle_t   * dle;
// DRF start: added for PCCard Kitchen
    RegPropertyValueSize    propertySize;
    InterruptSetMember      our_ist;
    InterruptHandler        old_interrupt_handler;
    void *                  refCon;
    OSStatus                status;
 
#ifdef  ENETADDRINATTRIBUTE
//  ethernet address is in attribute memory, but not CIS
    LogicalAddress          attribute_window_base;
    ByteCount               attribute_window_size;
    PCCardAccessSpeed       attribute_window_speed;
    PCCardWindowOffset      attribute_window_offset;
    PCCardWindowID          attribute_window_id;
#else
//  ethernet address is in CISTPL_FUNCE
    PCCardTupleIterator     tupleIterator;
    char                    tupleBuffer[MAX_TUPLE_SIZE];
    UInt32                  tupleBufferSize;
    PCCardTupleKind         foundTuple;
    UInt32                  foundTupleDataSize;
#endif
// DRF end
 
    loop_debug((trace_args "InitStreamModule" trace_eol));
 
    /* Allocate the board_t structure for this device */
    board = (board_t *)OTAllocMem(sizeof(board_t));
    if (!board)
        return false;
 
    /* These are the hardware start/stop/reset functions */
    bzero((char *)board, sizeof(board_t));
    dle = &board->board_dle;
    dle->dle_hw.dlehw_start = loop_hw_start;
    dle->dle_hw.dlehw_stop = loop_hw_stop;
    dle->dle_hw.dlehw_address_filter_reset = loop_hw_address_filter_reset;
    dle->dle_hw.dlehw_send_error = NULL;
    dle->dle_hw.dlehw_recv_error_flags = 0;
 
    /*
     * Suggestions:
     * - Install interrupt vectors in case any memory allocations are
     *  required.  Interrupts should not be enabled until the
     *  board start routine is called.
     * - Reset the hardware to a known state.  If the hardware does
     *  not respond, then return false.
     * - Read the Ethernet address from the board's ROM area.  This
     *  address should be copied into both the dle_factory_addr
     *  field and the dle_current_addr field.
     */
 
// DRF start: added for PCCard Kitchen
 
    /* install interrupt handler */
 
    loop_debug((trace_args "about to install interrupt handler!" trace_eol));
 
    propertySize = sizeof(InterruptSetMember);
    status = RegistryPropertyGet(our_id, kISTPropertyName, &our_ist, &propertySize);
    if (status != noErr) {
        loop_debug((trace_args "InitStreamModule install interrupt vector: can't get driver-ist property." trace_eol));
        return status;
    }
 
    status = GetInterruptFunctions(our_ist.setID, our_ist.member,
                                    &refCon, &old_interrupt_handler, &gInterruptEnabler, &gInterruptDisabler );
    if (status != noErr) {
        loop_debug((trace_args "InitStreamModule install interrupt vector: can't get interrupt functions." trace_eol));
        return status;
    }
 
    status = InstallInterruptFunctions(our_ist.setID, our_ist.member,
                                    NULL, board_intr, gInterruptEnabler, gInterruptDisabler );
    if (status != noErr) {
        loop_debug((trace_args "InitStreamModule install interrupt vector: can't set interrupt functions." trace_eol));
        return status;
    }
 
    /* get base address */
 
//  DebugStr("\p about to get base address!");
 
    propertySize = sizeof(gCardBaseAddress);
    status = RegistryPropertyGet(our_id, kAAPLDeviceLogicalAddress, &gCardBaseAddress, &propertySize);
    if (status != noErr) {
        loop_debug((trace_args "InitStreamModule install interrupt vector: can't get AAPL,address property." trace_eol));
        return status;
    }
 
 
//  Get Ethernet Hardware Address
 
#ifdef  ENETADDRINATTRIBUTE
//  If your card has ethernet hardware address in attribute memory, but NOT in a tuple, then use the following:
 
        /* IMPORTANT NOTE */
        // the following values are for compilation purposes only.  When accessing the ethernet address
        // from attribute memory, the following values must be set as appropriate for your card.
        
    attribute_window_size = 4096;
    attribute_window_speed = 600;
    attribute_window_offset = 0;
    status = PCCardRequestWindow(our_id, kPCCardAttributeMemorySpace, &attribute_window_base, &attribute_window_size,
                            &attribute_window_speed, &attribute_window_offset, &attribute_window_id);
    if (status != noErr) {
        loop_debug((trace_args "InitStreamModule get ethernet address: can't request attribute window" trace_eol));
        return status;
    }
 
    status = PCCardReleaseWindow(attribute_window_id);
 
        // save the Ethernet BIA into the dle structure as specified in the DLPI template note
    bcopy(dle->dle_factory_addr, attribute_window_base, 6);
    bcopy(dle->dle_current_addr, attribute_window_base, 6);
 
#else
//  if your card has ethernet hardware address in CISTPL_FUNCE, it is much easier
 
    tupleIterator = PCCardNewTupleIterator();
    if (tupleIterator == NULL)  {
        loop_debug((trace_args "InitStreamModule get ethernet address: can't make tuple iterator" trace_eol));
        return status;
    }
 
    tupleBufferSize = sizeof(tupleBuffer);
    status = PCCardGetFirstTuple(our_id, CISTPL_FUNCE, tupleIterator, &tupleBuffer[0], &tupleBufferSize,
                                &foundTuple, &foundTupleDataSize);  
 
    while (status == noErr) {
        // we found a CISTPL_FUNCE tuple
        
        DebugStr("\pFound CISTPL_FUNCE");       //  for RATOC card, the address is stored starting at the 5th byte
        
        if (1)  {
            my_hardware_addr[0] = tupleBuffer[5];
            my_hardware_addr[1] = tupleBuffer[6];
            my_hardware_addr[2] = tupleBuffer[7];
            my_hardware_addr[3] = tupleBuffer[8];
            my_hardware_addr[4] = tupleBuffer[9];
            my_hardware_addr[5] = tupleBuffer[10];
 
            DebugStr("\pgot hardware address");
    
            break;
        }
                
        tupleBufferSize = sizeof(tupleBuffer);
        status = PCCardGetNextTuple(our_id, CISTPL_FUNCE, tupleIterator, &tupleBuffer[0], &tupleBufferSize,
                                &foundTuple, &foundTupleDataSize);  
    }
 
    PCCardDisposeTupleIterator(tupleIterator);
 
        // save the Ethernet BIA into the dle structure as specified in the DLPI template note
    bcopy(dle->dle_factory_addr, my_hardware_addr, 6);
    bcopy(dle->dle_current_addr, my_hardware_addr, 6);
 
#endif
 
// DRF end
 
 
    /*
     * Allow the DLPI common code to initialize the dle fields. Once
     * dle_init is called, dle_terminate must be called before freeing
     * the dle structure.  There is private memory allocated for each
     * dle that needs to be freed.
     */
    dle_init(dle, 0);
    board_global = board;
 
    return true;
}
 
void
TerminateStreamModule (void)
{
    board_t * board = board_global;
 
    loop_debug((trace_args "TerminateStreamModule" trace_eol));
 
    /*
     * Suggestions:
     * - Remove interrupt vectors.
     * - Reset the hardware to a known state.
     */
 
    board_global = (board_t *)NULL;
    dle_terminate(&board->board_dle);
    OTFreeMem(board);
}
 
OTResult
ValidateHardware (RegEntryID * our_id)
{
    #pragma unused(our_id)
    loop_debug((trace_args "ValidateHardware" trace_eol));
    return kOTNoError;  
}
 
/* STREAMS close routine. */
int loop_close (queue_t * q, int flag, cred_t * credp)
{
    #pragma unused(flag,credp)
    loop_debug((trace_args "loop_close(%x), dcl_dle %x" trace_eol
        , q, ((dcl_t *)q->q_ptr)->dcl_hw ));
    /*
     * NOTE: there is probably not much else that needs to be done
     * in this routine.  If you need to know about the number of
     * open instances, dle_refcount is the number of streams still
     * referencing the device (decremented in dle_close).
     */
    return dle_close(q);
}
 
void loop_hw_address_filter_reset (void * boardvp, dle_addr_t * addr_list
            , ulong addr_count, ulong promisc_count
            , ulong multi_promisc_count, ulong accept_broadcast
            , ulong accept_error)
{
    #pragma unused(addr_count,accept_broadcast,accept_error)
    board_t     * board = boardvp;
    dle_t       * dle = &board->board_dle;
    dle_addr_t  * dlea;
    unsigned char   * first_phys_addr = 0;
    uint        phys_addr_count = 0;
 
    /* Calculate the new logical address filter for multicast addresses. */
    for (dlea = addr_list; dlea; dlea = dlea->dlea_next) {
        if (dlea->dlea_addr[0] & 0x1) {
            /*
             * If the address is a multicast address, then set
             * the right bits in the new filter.
             */
        } else {
            /*
             * Additional physical address.  This does not happen
             * with the first version of the DLPI template code.
             * However, at some future time, we may want to
             * support multiple physical addresses on one
             * hardware tap.
             */
            if (!first_phys_addr)
                first_phys_addr = dlea->dlea_addr;
            phys_addr_count++;
        }
    }
 
    if (first_phys_addr) {
        /*
         * If there were any physical, non-multicast addresses in
         * the list, then check to see if the first one is different
         * from the current one.  If so, copy the new address into
         * dle_current_addr and take whatever steps are necessary
         * with the hardware to change the physical address.
         */
        ;
    }
 
    /*
     * Compare the new address filter with the old one.  If the new
     * one is different, then set the hardware appropriately.
     */
 
    /*
     * If there are multiple physical addresses, then the board
     * probably needs to be put in a promiscuous state.
     */
    if (phys_addr_count > 1)
        promisc_count |= 1;
 
    if (promisc_count || multi_promisc_count)
        /* Set the board into promiscuous mode. */;
    else
        /* Clear any promiscuous mode that may be set*/;
    /*
     * Save the promiscuous setting in the board structure so that
     * updates to the hardware registers from loop_start or other
     * routines will be correct.
     */
}
 
/* Called by dlpiether code through dlehw_start. */
void loop_hw_start (void * boardvp)
{
    board_t * board = boardvp;
 
    /* Kick the board alive and allow receive interrupts. */
}
 
/* Called by dlpiether code through dlehw_stop. */
void loop_hw_stop (void * boardvp)
{
    #pragma unused(boardvp)
    /* Turn off interrupts and leave the hardware disabled. */
}
        
 
/* STREAMS open routine. */
int loop_open (queue_t * q, dev_t * devp, int flag, int sflag, cred_t * credp)
{
    int ret_code;
 
    loop_debug((trace_args "loop_open()" trace_eol));
    /*
     * This routine probably does not need to do anything other
     * than call dle_open.  dle_refcnt is incremented.
     */
    ret_code = dle_open(&board_global->board_dle, q, devp, flag, sflag, credp
            , sizeof(loop_t));
    loop_debug((trace_args "dle_open(%x) = %d, dcl_hw %x" trace_eol
        , &board_global->board_dle
        , ret_code
        , ((dcl_t *)q->q_ptr)->dcl_hw ));
    return ret_code;
}
 
 
/* 
 * Read-side service routine.
 * Pass all inbound packets upstream.  Messages are placed on
 * the read-side queue by dle_inbound.  Unless the hardware
 * requires something special, no additional code should be
 * required.  NOTE: Additional messages may be added to the
 * queue while this routine is running. If canputnext() fails,
 * messages are freed rather than put back on the queue.  
 * This is necessary since dle_inbound() will continue to add 
 * messages without checking flow control (it can't call canputnext()
 * from interrupt context).
 */
int loop_rsrv (q)
    queue_t * q;
{
    mblk_t * mp;
 
    while (mp = getq(q)) {
        /*
         * Private message to be passed back to the dlpiether
         * code.  This interface is required for supporting
         * 802.2 XID and Test packets.
         */
        if (mp->b_datap->db_type == M_CTL) {
            dle_rsrv_ctl(q, mp);
        } else if (canputnext(q))
            putnext(q, mp);
        else {
            freemsg(mp);
            flushq(q, FLUSHDATA);
            break;
        }
    }
    return 0;
}
 
/* Write-side put routine. Only handles M_DATA, handing others to dlpiether. */
int
loop_wput (queue_t * q, mblk_t * mp)
{
    loop_t  * loop;
    mblk_t  * first_mp;
    long    remaining;
    long    total;
    unsigned char   * xmt_buf=nil;
 
    loop = (loop_t *)q->q_ptr;
    if (mp->b_datap->db_type != M_DATA) {
        mp = dle_wput(q, mp);
        if (!mp)
            return 0;
        switch (mp->b_datap->db_type) {
        case M_DATA:
            break;      /* it's ready to send */
        case M_IOCNAK:
            /*
             * Any driver private ioctl's come back from
             * dle_wput() as an M_IOCNAK with ioc_error
             * set to EINVAL; the rest of the original M_IOCTL
             * is intact (including ioc_cmd & trailing M_DATAs).
             * It may be processed here.
             */
             qreply(q,mp);
             return 0;
        default:
            /* dle_wput() has formatted the reply for us */
            qreply(q, mp);
            return 0;
        }
    }
 
    /*
     * Copy the packet to transmit buffer.  This is an example
     * showing the copies and the calculation of the length
     * of the packet.  Code for actual hardware devices will
     * obviously need to be updated, at least to initialize
     * xmt_buf to point to the hardware transmit area.
     */
    remaining = MAX_PACKET_SIZE;
    first_mp = mp;
 
    do {
        unsigned char   * rptr = mp->b_rptr;
        int len = mp->b_wptr - rptr;
        if (len <= 0)
            continue;
        if (remaining < len) {
            /* packet too large */
            mp = dle_wput_ud_error(first_mp, DL_UNDELIVERABLE, 0);
            if (mp)
                qreply(q, mp);
            return 0;
        }
        remaining -= len;
        xmt_buf += len;
        board_bcopy_to_noncached(rptr, xmt_buf - len, len);
    } while ((mp = mp->b_cont)  &&  remaining);
 
    total = MAX_PACKET_SIZE - remaining;
 
    /* Fill in the 802 length field if it is zero. */
    {
        unsigned char   * up;
        up = &xmt_buf[-total];
        if (!(up[12] | up[13])) 
        {
            uint    adjusted_total = total - 14;
            up[12] = (unsigned char)(adjusted_total >> 8);
            up[13] = (unsigned char)(adjusted_total & 0xff);
        }
    }
 
    if (total < MIN_PACKET_SIZE) 
    {
        /*
         * If the packet is less than the minimum transmit
         * size, then you need to pad the packet.  Hopefully
         * this is just of matter of setting the hardware
         * to pad automatically.
         */
         ;
    }
 
    /* Trigger the hardware transmit. */
    /* Increment the appropriate MIB interface statistics. */
    ((dle_t *)loop->loop_dle)->dle_istatus.bytes_sent += total;
    if (first_mp->b_rptr[0] & 0x1) {
        unsigned char   * rptr = first_mp->b_rptr;
        if ((rptr[0] & rptr[1] & rptr[2] & rptr[3] & rptr[4]
             & rptr[5]) == 0xFF)
            ((dle_t *)loop->loop_dle)->dle_istatus.broadcast_frames_sent++;
        else
            ((dle_t *)loop->loop_dle)->dle_istatus.multicast_frames_sent++;
    } else
        ((dle_t *)loop->loop_dle)->dle_istatus.unicast_frames_sent++;
 
    freemsg(first_mp);
    return 0;
}