linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
From: "leeyang" <leeyang@ycig.com>
To: "HoJin" <jhfami@yahoo.co.kr>
Cc: <linuxppc-embedded@lists.linuxppc.org>
Subject: Re: HDLC driver for MPC8xx SCC
Date: Tue, 25 May 2004 09:48:13 +0800	[thread overview]
Message-ID: <002501c441fa$579fb3d0$e100a8c0@ycigrnd.ycig.com> (raw)
In-Reply-To: 40B295BD.9040500@yahoo.co.kr

[-- Attachment #1: Type: text/plain, Size: 563 bytes --]

http://qslinux.org/docs/snmc/hdlc/

check this


----- Original Message -----
From: "HoJin" <jhfami@yahoo.co.kr>
To: <linuxppc-embedded@lists.linuxppc.org>
Sent: Tuesday, May 25, 2004 8:39 AM
Subject: HDLC driver for MPC8xx SCC


>
> Hi,
> I'm working on my custom mpc860 board, and kernel 2.4. I want to
> implement sync. HDLC driver. I have found old HDLC driver for mpc860 but
> it is not sync and doesn't works well.
>
> Are there any nice driver for mpc860 HDLC driver?
> Any helps and advices are welcome. :)
>
> --
> HoJin, Yu
>
> jhfami@yahoo.co.kr
>
>
>

[-- Attachment #2: hdlcppp.c --]
[-- Type: application/octet-stream, Size: 32315 bytes --]

// $Header: /projects/cvsroot/snmc/qslinux/drivers/net/hdlcppp.c,v 1.2 2000/08/10 15:56:07 dnevil Exp $

// Copyright 2000 SNMC
// Simple Network Magic Corporation
		

// Synchronous (HDLC) PPP Driver
// Author: Daris A Nevil

// This driver provides RX/TX functionality for HDLC frames
// on the QS850 SCC controller.  Each SCC is configured
// appropriately for PPP packet transport according to RFC 1662.

// $Log: hdlcppp.c,v $
// Revision 1.2  2000/08/10 15:56:07  dnevil
// Removed compiler warnings.
//
// Revision 1.1.1.1  2000/08/10 14:34:25  dnevil
// New files.
//

// Standard includes

// Local includes

#define HAVE_CHANGE_MTU

#include <linux/types.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/netdevice.h>
#include <linux/kerneld.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/processor.h>
#include <asm/system.h>
#include <asm/semaphore.h>
#include <asm/8xx_immap.h>
#include <asm/mpc8xx.h>
#include <asm/commproc.h>
#include <asm/qspin.h>
#include <linux/syncppp.h>
#include <asm/cpm.h>
#include <asm/hdlcppp.h>


//#define HDLC_PPP_DEBUG

#ifdef HDLC_PPP_DEBUG
#define DEBUG(args...) printk(args)
#else
#define DEBUG(args...)
#endif

#define RX_RING_SIZE 8
#define TX_RING_SIZE 8

// Maximum size for RX Buffers
// Must be a multiple of 4
#define MAX_RX_BUFF_SIZE 1560 // 390*4

// This value must be <= MAX_RX_BUFF_SIZE
#define HDLC_PPP_MTU (1550+8)

#define NUM_SCCS	4	// Number of SCC Channels
#define SCC_PARAM_SIZE	0x100	// SCC Parameter RAM size

#define SYNC_PPP_MODULE	"syncppp"

// Defines number of RX Frames before an interrupt is generated
#define RX_FRAME_THRESHOLD 1

// HDLC Addresses and Masks
#define HDLC_C_MASK	0xF0B8	// CRC Constant
#define HDLC_C_PRES	0xFFFF	// CRC Preset
#define HDLC_ADDR_MASK	0x00FF	// Address Mask (8-bit)
#define HDLC_AS_ADDR	0x00FF	// All-Stations Address
#define HDLC_CM_ADDR	0x008F	// Cisco Multicast Address
#define HDLC_CU_ADDR	0x000F	// Cisco Unicast Address

#define TX_TIMEOUT	200	// Ticks

//-------------------------------------------------------------------
// Local structures

typedef struct {

	// SCC device number (1-4)
	int			scc_num;
	int			scc_param_index;

	// Device structure for SYNC PPP
	struct ppp_device	pppdev;

	// Device Statistics
	struct net_device_stats	stats;

	// Buffer descriptors
	// TX Buffers and descriptors
	volatile cbd_t*	tx_bd_base; // Start of TX Buffer Descriptors
	volatile cbd_t*	tx_bd[TX_RING_SIZE];
	int		tx_head;
	struct sk_buff*	tx_skbuff[TX_RING_SIZE];

	// RX Buffers and descriptors
	volatile cbd_t*	rx_bd_base; // Start of RX Buffer Descriptors
	volatile cbd_t*	rx_bd[RX_RING_SIZE];
	int		rx_tail;
	struct sk_buff*	rx_skbuff[RX_RING_SIZE];

	// Bottom Half task queue
	struct tq_struct	rx_bh_tq;
	struct tq_struct	tx_bh_tq;

	} hdlc_channel_t;

typedef struct
	{
	char*		name;
	uint		pram_index;
	ushort		chan_cmd;
	ushort		irq_vector;
	} SCC_Params_t;

//-------------------------------------------------------------------
// Function Prototypes
void sync_ppp_init(void);
#ifdef MODULE
int init_module(void);
#else
int hdlc_ppp_init(void);
#endif
#ifdef MODULE
void cleanup_module(void);
#else
void hdlc_ppp_cleanup_module(void);
#endif
static void hdlc_ppp_interrupt(void *dev_id);
static int hdlc_ppp_hard_start_xmit(struct sk_buff* skb, struct device* dev);
static struct net_device_stats* hdlc_ppp_get_stats(struct device* dev);
static int hdlc_ppp_ioctl(struct device* dev, struct ifreq* ifr, int cmd);
static int hdlc_ppp_change_mtu(struct device* dev, int new_mtu);
static int hdlc_ppp_stop(struct device* dev);
static int hdlc_ppp_alloc_rx_ring(struct device* dev);
static struct sk_buff* hdlc_ppp_alloc_skb(int mtu);
static void hdlc_ppp_restart_rx(struct device* dev);
static void hdlc_ppp_restart_tx(struct device* dev);
static int hdlc_ppp_init_local(struct device* dev);
static int hdlc_ppp_open(struct device* dev);
static void hdlc_ppp_tx_bh(void* dev_id);
static void hdlc_ppp_rx_bh(void* dev_id);

//-------------------------------------------------------------------
// Global initializers

#define PPP_HDLC_CHANNELS	4

static hdlc_channel_t MyChan[PPP_HDLC_CHANNELS];

static SCC_Params_t SCC_Params[PPP_HDLC_CHANNELS] = {
	{"hdlcppp1", PROFF_SCC1, CPM_CR_CH_SCC1, CPMVEC_SCC1},
	{"hdlcppp2", PROFF_SCC2, CPM_CR_CH_SCC2, CPMVEC_SCC2},
	{"hdlcppp3", PROFF_SCC3, CPM_CR_CH_SCC3, CPMVEC_SCC3},
	{"hdlcppp4", PROFF_SCC4, CPM_CR_CH_SCC4, CPMVEC_SCC4},
};

// Module Configuration Parameters
MODULE_AUTHOR("dnevil@snmc.com");
MODULE_DESCRIPTION("SCC HDLC Driver for PPP");


//--------------------------------------------------------------
// init_module()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	None.
//
// Initialize the SCC port for operation in HDLC mode suitable
// for transport of PPP according to RFC 1662.

#ifdef MODULE
int init_module(void)
#else
int hdlc_ppp_init(void)
#endif
{
	int i;
	int result;
	struct device* dev;
	volatile cpm8xx_t* cp = cpmp;

	DEBUG("hdlc_ppp: init_module()\n");
	printk(KERN_INFO "SCC HDLC Driver for PPP v1.0\n");
	printk(KERN_INFO "(c) Copyright 2000, Simple Network Magic Corporation\n");

#if 0
	// Request the SYNC PPP Module
	result = request_module(SYNC_PPP_MODULE);
	if (result)
		return(result);
#endif

	for (i = 0; i < PPP_HDLC_CHANNELS; ++i)
		{
		__clear_user(&MyChan[i], sizeof MyChan[0]);

		// Save the SCC Channel number
		MyChan[i].scc_num = i+1;
		MyChan[i].scc_param_index = i;

		// Attach the SYNC PPP driver to this device
		sppp_attach(&MyChan[i].pppdev);
		dev = &MyChan[i].pppdev.dev;

		// Get pointer to base of SCC control registers
		// (index, not number)
		dev->base_addr = (unsigned long)&cp->cp_scc[i];

		// Get beginning of my SCC's Parameter Ram
		dev->mem_start = (unsigned long)&
			cp->cp_dparam[SCC_Params[i].pram_index];

		// Get end of my SCC's Parameter Ram
		dev->mem_end = dev->mem_start + SCC_PARAM_SIZE - 1;

		// Fill out the function pointers
		dev->name = SCC_Params[i].name;
		dev->init = hdlc_ppp_init_local;
		dev->open = hdlc_ppp_open;
		dev->stop = hdlc_ppp_stop;
//		dev->set_config = hdlc_ppp_set_config;
		dev->set_config = NULL;
		dev->hard_start_xmit = hdlc_ppp_hard_start_xmit;
		dev->get_stats = hdlc_ppp_get_stats;
		dev->set_multicast_list = NULL;
		dev->do_ioctl = hdlc_ppp_ioctl;
//		dev->neigh_setup = hdlc_ppp_neigh_setup;
		dev->set_mac_address = NULL;
		dev->change_mtu = hdlc_ppp_change_mtu;
		dev->rebuild_header = NULL;
		dev->priv = (void*)&MyChan[i];

		// Register my device
		result = register_netdev(dev);
		if (result)
			{
			printk(KERN_WARNING "%s: register_netdev failed.\n",
				dev->name);

			sppp_detach(&MyChan[i].pppdev.dev);
			return(result);
			}
		}

	// Initialize the sync ppp layer
	sync_ppp_init();

	// Success
	return(0);
}

//--------------------------------------------------------------
// cleanup_module()
//
// Return:
//	None.
//
// Input Parameters:
//	None.
//
// Remove the device from the list of network devices.
// Return allocated resources back to the system pool.

#ifdef MODULE
void cleanup_module(void)
#else
void hdlc_ppp_cleanup_module(void)
#endif
{
	int i;

	DEBUG("hdlc_ppp: cleanup_module()\n");

	for (i = 0; i < PPP_HDLC_CHANNELS; ++i)
		{
		// Detach the PPP Layer
		sppp_detach(&MyChan[i].pppdev.dev);

		// Unregister the device
		// sys_delete_module() will check MOD_IN_USE
		unregister_netdev(&MyChan[i].pppdev.dev);
		}
}


//--------------------------------------------------------------
// hdlc_ppp_init()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Initialize the SCC port for operation in HDLC mode suitable
// for transport of PPP according to RFC 1662.

static int hdlc_ppp_init_local(struct device* dev)
{
	DEBUG("hdlc_ppp_init()\n");

	// Initialize device driver buffers
	dev_init_buffers(dev);

	// Success
	return(0);
}

//--------------------------------------------------------------
// hdlc_ppp_open()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Open the device

static int hdlc_ppp_open(struct device* dev)
{
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile cpm8xx_t* cp = cpmp;
	volatile scc_t* sccp = (scc_t*)dev->base_addr;
	volatile scc_hdlc_t* hp = (scc_hdlc_t*)dev->mem_start;
	int i;
	int result;

	DEBUG("%s: hdlc_ppp_open()\n", dev->name);

	// Disable rx & tx
	sccp->scc_gsmrl &= ~(SCC_GSMRL_ENR | SCC_GSMRL_ENT);

	// Make sure the parameter RAM area is not already allocated
	result = check_region(dev->mem_start, SCC_PARAM_SIZE);
	if (result)
		return(result);

	// Allocate the parameter RAM
	request_region(dev->mem_start, SCC_PARAM_SIZE, dev->name);

	// Configure I/O Pins
	// This will be left to user code

#if 0
	// Init the SI Clock Route Register (SICR) for SCC3
	// Connect SCC3 to NMSI
	// Transmit Clock (CLK3) = BRGO2
	// Receive Clock (CLK1) = QS850_PIN_A20
	cp->cp_sicr &= 0x0000FFFF;
	cp->cp_sicr |= 0x00210000;
#else
	// Init the SI Clock Route Register (SICR) for SCC3
	// Connect SCC3 to NMSI
	// Transmit Clock (CLK3) = QS850_PIN_A19
	// Receive Clock (CLK3) = QS850_PIN_A19
	cp->cp_sicr &= 0x0000FFFF;
	cp->cp_sicr |= 0x00360000;
#endif

	// Allocate RX Buffer descriptors
	i = m8xx_cpm_dpalloc(sizeof(cbd_t) * RX_RING_SIZE);
	if (i == CPM_DP_NOSPACE)
		{
		printk("hdlc_ppp: No more CPM DPRAM\n");
		result = -ENOMEM;
		goto fail_rx_dpram;
		}

	hp->genscc.scc_rbase = i;
	chan->rx_bd_base = (cbd_t*)&cp->cp_dpmem[i];

	// Allocate TX Buffer descriptors
	i = m8xx_cpm_dpalloc(sizeof(cbd_t) * TX_RING_SIZE);
	if (i == CPM_DP_NOSPACE)
		{
		printk("hdlc_ppp: No more CPM DPRAM\n");
		result = -ENOMEM;
		goto fail_tx_dpram;
		}

	hp->genscc.scc_tbase = i;
	chan->tx_bd_base = (cbd_t*)&cp->cp_dpmem[i];

	// Init Function Code registers (Big Endian)
	hp->genscc.scc_rfcr = SCC_EB;
	hp->genscc.scc_tfcr = SCC_EB;

	// Set the RX Buffer maximum size
	hp->genscc.scc_mrblr = MAX_RX_BUFF_SIZE;

	// Set the MTU
	hp->mflr = HDLC_PPP_MTU;

	// Set the CRC Constant and Preset registers
	hp->c_mask = HDLC_C_MASK;
	hp->c_pres = HDLC_C_PRES;

	// Set the All-Stations Address (used for PPP)
	// Also set Cisco Multicast and Cisco Unicast addresses
	hp->hmask = HDLC_ADDR_MASK;
	hp->haddr1 = HDLC_AS_ADDR;
	hp->haddr2 = HDLC_CM_ADDR;
	hp->haddr3 = HDLC_CU_ADDR;
	hp->haddr4 = HDLC_AS_ADDR;

	// Clear the pm counters
	hp->disfc = 0;
	hp->crcec = 0;
	hp->abtsc = 0;
	hp->nmarc = 0;
	hp->retrc = 0;

	// Set the RX Frame Threshold
	hp->rfthr = RX_FRAME_THRESHOLD;

	// Set the Protocol Specific Mode Register
	// NOF = 0 (1 flag between adjacent frames)
	// CRC = 16-bit
	// No Retransmission
	// FSE = Normal
	// DRT = Normal
	// No HDLC Bus mode
	// BRM = Ignored
	// MFF = Normal
	sccp->scc_psmr = 0x0000;

	// Disable All Interrupts for this SCC
	sccp->scc_sccm = 0x0000;

	// Clear pending events
	sccp->scc_scce = 0xFFFF;

	// Install my interrupt handler
	cpm_install_handler(SCC_Params[chan->scc_param_index].irq_vector,
		hdlc_ppp_interrupt, dev);

	// Enable Interrupts for Transmit Error, Rx Received, and TXB
	sccp->scc_sccm = (SCCM_TXE | SCCM_RX | SCCM_RXF | SCCM_TX);

	// Set the GSMR_H and GSMR_L registers
	sccp->scc_gsmrh = 0;
	sccp->scc_gsmrl = SCC_GSMRL_MODE_HDLC;

	// Set the DSR register, just it case it has been modified
	// by another driver since reset
	sccp->scc_dsr = 0x7E7E;

	// Allocate buffers for all RX Buf Descr
	if ((result = hdlc_ppp_alloc_rx_ring(dev)))
		goto fail_buffer;

	// Initialize and start the Receiver and Transmitter
	hdlc_ppp_restart_rx(dev);
	hdlc_ppp_restart_tx(dev);

	// Complete driver-required state info
	dev->start = 1;
	dev->tbusy = 0;
	dev->interrupt = 0;

	// Adjust module count
	MOD_INC_USE_COUNT;

	// Open the PPP driver
	result = sppp_open(dev);
	if (result)
		{
		printk(KERN_WARNING "%s: could not open Sync PPP device.\n",
			dev->name);
		MOD_DEC_USE_COUNT;
		goto fail_buffer;
		}

	goto done;

fail_buffer:
printk("fail_buffer\n");

fail_rx_dpram:
printk("fail_rx_dpram\n");
	//m8xx_cpm_dpfree();

fail_tx_dpram:
printk("fail_tx_dpram\n");
	//m8xx_cpm_dpfree();

	release_region(dev->mem_start, SCC_PARAM_SIZE);

done:
	// Done
	return(result);
}


//--------------------------------------------------------------
// hdlc_ppp_stop()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Close the device and free all resources

static int hdlc_ppp_stop(struct device* dev)
{
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile scc_t* sccp = (scc_t*)dev->base_addr;
	volatile cpm8xx_t* cp = cpmp;
	int i;

	DEBUG("%s: hdlc_ppp_stop()\n", dev->name);

	// Close the Sync PPP driver
	sppp_close(dev);

	// Set the driver flags
	dev->tbusy = 1;
	dev->start = 0;

	// Send a Stop Transmit command to the CPM
	cp->cp_cpcr = mk_cr_cmd(SCC_Params[chan->scc_param_index].chan_cmd,
		CPM_CR_STOP_TX) | CPM_CR_FLG;
	while (cp->cp_cpcr & CPM_CR_FLG);

	// Disable rx & tx
	sccp->scc_gsmrl &= ~(SCC_GSMRL_ENR | SCC_GSMRL_ENT);

	// Free the socket buffers associated with the RX buffer rings
	for (i = 0; i < RX_RING_SIZE; ++i)
		{
		// Free any allocated socket buffers
		if (chan->rx_skbuff[i])
			dev_kfree_skb(chan->rx_skbuff[i]);

		chan->rx_skbuff[i] = 0;
		}

	// Free the socket buffers associated with the TX buffer rings
	for (i = 0; i < TX_RING_SIZE; ++i)
		{
		// Free any allocated socket buffers
		if (chan->tx_skbuff[i])
			dev_kfree_skb(chan->tx_skbuff[i]);

		chan->tx_skbuff[i] = 0;
		}

	// Release the Parameter Ram associated with this channel
	release_region(dev->mem_start, SCC_PARAM_SIZE);

	// Release RX & TX Buffer descriptors
	// m8xx_cpm_dpfree(hp->genscc.scc_rbase);
	// m8xx_cpm_dpfree(hp->genscc.scc_tbase);

	// Disable All Interrupts for this SCC
	sccp->scc_sccm = 0x0000;

	// Clear pending events
	sccp->scc_scce = 0xFFFF;


	// Uninstall the interrupt handler
//?

	// Update statistics
//?

	// Adjust module count
	MOD_DEC_USE_COUNT;

	// Done
	return(0);
}


//--------------------------------------------------------------
// hdlc_ppp_ioctl()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//	(IO) ifr - pointer to a structure for passing arguments
//	(I) cmd - ioctl() function specifier
//
// Perform special device control functions

static int hdlc_ppp_ioctl(struct device* dev,
	struct ifreq* ifr, int cmd)
{
	volatile scc_t* sccp = (scc_t*)dev->base_addr;

	DEBUG("%s: hdlc_ppp_ioctl()\n", dev->name);

	switch (cmd)
	{
	case HDLCPPPIOCNOLOOPECHO:
		sccp->scc_gsmrl &= ~(SCC_GSMRL_DIAG_LOOP | SCC_GSMRL_DIAG_ECHO);
		break;

	case HDLCPPPIOCLOOP:
		sccp->scc_gsmrl &= ~SCC_GSMRL_DIAG_ECHO;
		sccp->scc_gsmrl |= SCC_GSMRL_DIAG_LOOP;
		break;

	case HDLCPPPIOCECHO:
		sccp->scc_gsmrl &= ~SCC_GSMRL_DIAG_LOOP;
		sccp->scc_gsmrl |= SCC_GSMRL_DIAG_ECHO;
		break;

	case HDLCPPPIOCLOOPECHO:
		sccp->scc_gsmrl |= SCC_GSMRL_DIAG_LOOP | SCC_GSMRL_DIAG_ECHO;
		break;

	case HDLCPPPIOCTXCLKFALL:
		sccp->scc_gsmrl &= ~SCC_GSMRL_TCI;
		break;

	case HDLCPPPIOCTXCLKRISE:
		sccp->scc_gsmrl |= SCC_GSMRL_TCI;
		break;

	default:
		// Perform the Sync PPP ioctl() functions
		return(sppp_do_ioctl(dev, ifr, cmd));
	}

	// Success
	return(0);
}


//--------------------------------------------------------------
// hdlc_ppp_change_mtu()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//	(I) new_mtu - new size of the mtu
//
// Sets a new value for Maximum Transmission Unit (MTU).

static int hdlc_ppp_change_mtu(struct device* dev, int new_mtu)
{
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile scc_hdlc_t* hp = (scc_hdlc_t*)dev->mem_start;
	volatile scc_t* sccp = (scc_t*)dev->base_addr;
	int result;
	int i;

	DEBUG("%s: hdlc_ppp_change_mtu: new mtu=%d, old mtu=%d\n",
		dev->name, new_mtu, hp->mflr);

	// Stop invalid changes
	if ((new_mtu < 32) || (new_mtu > 65534))
		return(-EINVAL);

	// Disable the Receiver
	sccp->scc_gsmrl &= ~SCC_GSMRL_ENR;

	// Set the MTU
	hp->mflr = new_mtu;

	// Set the RX Buffer maximum size
	// Must be a multiple of 4 for the SCC in HDLC mode
	new_mtu /= 4;
	new_mtu += 2; // Add a little cushion
	new_mtu *= 4;
	hp->genscc.scc_mrblr = new_mtu;

	// Free the socket buffers associated with the RX buffer rings
	for (i = 0; i < RX_RING_SIZE; ++i)
		{
		// Free any allocated socket buffers
		if (chan->rx_skbuff[i])
			dev_kfree_skb(chan->rx_skbuff[i]);

		chan->rx_skbuff[i] = 0;
		}

	// Allocate new buffers
	if ((result = hdlc_ppp_alloc_rx_ring(dev)))
		return(result);

	// Restart Receiver
	hdlc_ppp_restart_rx(dev);

	// Success
	return(0);
}


//--------------------------------------------------------------
// hdlc_ppp_alloc_rx_ring()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Allocate buffers for each buffer descriptor in the RX ring.
// The receiver must be restarted after calling this function.

static int hdlc_ppp_alloc_rx_ring(struct device* dev)
{
	int i;
	volatile cbd_t* bdp;
	struct sk_buff*	newskb;
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile scc_hdlc_t* hp = (scc_hdlc_t*)dev->mem_start;

	// Initialize the RX Buffer Descriptor Ring
	bdp = chan->rx_bd_base;
	for (i = 0; i < RX_RING_SIZE; ++i, ++bdp)
		{
		newskb = hdlc_ppp_alloc_skb(hp->genscc.scc_mrblr);
		if (!newskb)
			{
			printk("cannot alloc socket buffer\n");
			goto fail_rx_buffer;
			}

		// Assign physical memory to each RX Buffer Descriptor
		bdp->cbd_bufaddr = __pa(newskb->data);
		chan->rx_skbuff[i] = newskb;
		}

	// Success
	return(0);

fail_rx_buffer:

	for (i = 0; i < RX_RING_SIZE; ++i)
		{
		// Free any allocated socket buffers
		if (chan->rx_skbuff[i])
			dev_kfree_skb(chan->rx_skbuff[i]);

		chan->rx_skbuff[i] = 0;
		}

	return(-ENOMEM);
}


//--------------------------------------------------------------
// hdlc_ppp_alloc_skb()
//
// Return:
//	Pointer to a new socket buffer.
//
// Input Parameters:
//	(I) mtu - specifies the size in bytes of the current MTU
//
// Allocates a new socket buffer large enough to receive a complete
// frame of the current MTU size.  The buffer is pre-flush from
// the cache so that data received from the SCC is not corrupted
// later.

static struct sk_buff* hdlc_ppp_alloc_skb(int mtu)
{
	struct sk_buff*	newskb;

	// Allocate a socket buffer
	newskb = dev_alloc_skb(mtu);

	if (newskb)
		{
		// Push the data cache so updates are made now from cache
		// (we don't want data clobbered later)
		flush_dcache_range((ulong)newskb->data,
			(ulong)(newskb->data + mtu));
		}

	return(newskb);
}

//--------------------------------------------------------------
// hdlc_ppp_restart_rx()
//
// Return:
//	None.
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Restarts (or starts for the first time) the Receiver of the
// specified SCC.

static void hdlc_ppp_restart_rx(struct device* dev)
{
	volatile cpm8xx_t* cp = cpmp;
	volatile scc_t* sccp = (scc_t*)dev->base_addr;
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile cbd_t* bdp;
	int i;

	// Disable the receiver
	sccp->scc_gsmrl &= ~SCC_GSMRL_ENR;

	// Re-INIT RX
	// This command lets the CPM know it must reinitialize the SCC
	// with the new parameter RAM values.
	// This intializes the rbptr register
	cp->cp_cpcr = mk_cr_cmd(SCC_Params[chan->scc_param_index].chan_cmd,
		CPM_CR_INIT_RX) | CPM_CR_FLG;
	while (cp->cp_cpcr & CPM_CR_FLG);

	// Synchronize the local state machine with the CPM
	chan->rx_tail = 0;

	// Initialize the RX Buffer Descriptor Ring
	bdp = chan->rx_bd_base;
	for (i = 0; i < RX_RING_SIZE; ++i, ++bdp)
		{
		// Save the indexed pointer to the bd
		chan->rx_bd[i] = bdp;

		if (i == (RX_RING_SIZE - 1))
			bdp->cbd_sc = BD_HDLC_RX_EMPTY | BD_HDLC_RX_INTRPT |
				BD_HDLC_RX_WRAP;
		else
			bdp->cbd_sc = BD_HDLC_RX_EMPTY | BD_HDLC_RX_INTRPT;
		}

	// Enable Receive processing
	// Hunt Mode is automatically enetered
	sccp->scc_gsmrl |= SCC_GSMRL_ENR;
}

//--------------------------------------------------------------
// hdlc_ppp_restart_tx()
//
// Return:
//	None.
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Restarts (or starts for the first time) the Transmitter of the
// specified SCC.

static void hdlc_ppp_restart_tx(struct device* dev)
{
	volatile cpm8xx_t* cp = cpmp;
	volatile scc_t* sccp = (scc_t*)dev->base_addr;
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile cbd_t* bdp;
	int i;

	// Send the Stop Transmission command to the CPM for this SCC
	cp->cp_cpcr = mk_cr_cmd(SCC_Params[chan->scc_param_index].chan_cmd,
		CPM_CR_STOP_TX) | CPM_CR_FLG;
	while (cp->cp_cpcr & CPM_CR_FLG);

	// Disable transmission
	sccp->scc_gsmrl &= ~SCC_GSMRL_ENT;

	// Re-INIT TX command
	// This command lets the CPM know it must reinitialize the SCC
	// with the new parameter RAM values.
	// This intializes the tbptr register
	cp->cp_cpcr = mk_cr_cmd(SCC_Params[chan->scc_param_index].chan_cmd,
		CPM_CR_INIT_TX) | CPM_CR_FLG;
	while (cp->cp_cpcr & CPM_CR_FLG);

	// Synchronize the local state machine with the CPM
	chan->tx_head = 0;

	// Initialize the TX Buffer Descriptor Ring
	bdp = chan->tx_bd_base;
	for (i = 0; i < TX_RING_SIZE; ++i, ++bdp)
		{
		// Save the indexed pointer to the bd
		chan->tx_bd[i] = bdp;
#if 0
		if (i == (TX_RING_SIZE - 1))
			{
			bdp->cbd_sc = BD_HDLC_TX_WRAP;
			}
		else
			{
			bdp->cbd_sc = 0;
			}
#else
		bdp->cbd_sc = 0;
#endif
		// No pointers yet
		bdp->cbd_bufaddr = 0;

		// Free any allocated socket buffers
		if (chan->tx_skbuff[i])
			{
			dev_kfree_skb(chan->tx_skbuff[i]);
			chan->stats.tx_dropped++;
			}

		chan->tx_skbuff[i] = 0;
		}

	// Enable Transmit processing
	sccp->scc_gsmrl |= SCC_GSMRL_ENT;

	// TX no longer busy
	dev->tbusy = 0;
}

//--------------------------------------------------------------
// hdlc_ppp_get_stats()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) dev - pointer to this driver's device structure
//
// Return the device statistics

static struct net_device_stats* hdlc_ppp_get_stats(struct device* dev)
{
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;

	DEBUG("%s: hdlc_ppp_get_stats()\n", dev->name);

	return(&chan->stats);
}


//--------------------------------------------------------------
// hdlc_ppp_hard_start_xmit()
//
// Return:
//	0 on success, negative integer on failure
//
// Input Parameters:
//	(I) skb - pointer to a buffer to transmit
//	(I) dev - pointer to this driver's device structure
//
// Close the device and free all resources

static int hdlc_ppp_hard_start_xmit(struct sk_buff* skb, struct device* dev)
{
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile cbd_t* bdp;
	int i;

	DEBUG("%s: hdlc_ppp_hard_start_xmit()\n", dev->name);

	if (dev->tbusy)
		{
		int tickssofar = jiffies - dev->trans_start;
		if (tickssofar < TX_TIMEOUT)
			{
			dev_kfree_skb(skb);
			return(-EBUSY);
			}

		printk("%s: transmit timed out, restarting\n", dev->name);
		chan->stats.tx_errors++;

		// Restart the transmitter
		hdlc_ppp_restart_tx(dev);
		}

	// Not exactly sure what this code does.  It is illustrated
	// on p321 of "Linux Device Drivers".
	if (skb == NULL)
		return(0);

	// Get the head of the queue
	i = chan->tx_head;
	bdp = chan->tx_bd[i];

	if (bdp->cbd_sc & BD_HDLC_TX_READY)
		{
		printk("%s: no tx bd's available\n", dev->name);

		// The transmitter is now busy
		dev->tbusy = 1;

		// We loose this packet
		dev_kfree_skb(skb);
		chan->stats.tx_dropped++;

		return(-EBUSY);
		}

	// We found a bd we can use, bump the head index
	++chan->tx_head;
	if (chan->tx_head >= TX_RING_SIZE)
		chan->tx_head = 0;

	// Set buffer length and pointer
	chan->tx_skbuff[i] = skb;
	bdp->cbd_datlen = skb->len;

	bdp->cbd_bufaddr = __pa(skb->data); // Use the physical address

	// Push the data cache so the CPM does not get stale data
	flush_dcache_range((ulong)skb->data, (ulong)(skb->data + skb->len));

	// Clear status flags
	bdp->cbd_sc &= ~BD_HDLC_TX_STATS;

	// Record start time of transmission
	dev->trans_start = jiffies;

	// Send the packet
	if (i == (TX_RING_SIZE - 1))
		{
		bdp->cbd_sc |= BD_HDLC_TX_READY | BD_HDLC_TX_INTRPT |
			BD_HDLC_TX_LAST | BD_HDLC_TX_CRC | BD_HDLC_TX_WRAP;
		}
	else
		{
		bdp->cbd_sc |= BD_HDLC_TX_READY | BD_HDLC_TX_INTRPT |
			BD_HDLC_TX_LAST | BD_HDLC_TX_CRC;
		}


	// Update statistics
	chan->stats.tx_bytes += skb->len;
	chan->stats.tx_packets++;

	// Done
	return(0);
}



//--------------------------------------------------------------
// hdlc_ppp_interrupt()
//
// Return:
//	None.
//
// Input Parameters:
//	(I) dev_id - pointer to this driver's device structure
//
// Close the device and free all resources

static void hdlc_ppp_interrupt(void *dev_id)
{
	struct device* dev = (struct device*)dev_id;
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile scc_t* sccp = (scc_t*)dev->base_addr;
	ushort int_events;

	DEBUG("%s: Entering ISR\n", dev->name);

	// Print message if we are re-entering
	if (dev->interrupt)
		{
		printk("%s: re-entering the interrupt handler\n",
			dev->name);
		}

	// Indicate we are in our ISR
	dev->interrupt = 1;

	// Get the interrupt events
	int_events = sccp->scc_scce;

	//DEBUG("%s: sccp->scc_scce=0x%x\n", dev->name, int_events);

	// Clear off these event
	sccp->scc_scce = int_events;

	if (int_events & SCCM_BSY)
		{
		chan->stats.rx_errors++;
		chan->stats.rx_dropped++;
		DEBUG("%s: SCCM_BSY=%ld\n", dev->name, chan->stats.rx_errors);
		}

	if (int_events & (SCCM_RX|SCCM_RXF))
		{
		// Queue the task into the immediate bottom half queue
		chan->rx_bh_tq.routine = hdlc_ppp_rx_bh;
		chan->rx_bh_tq.data = (void*)dev;
		queue_task(&chan->rx_bh_tq, &tq_immediate);
		mark_bh(IMMEDIATE_BH);
		}

	if (int_events & SCCM_TXE)
		{
		chan->stats.tx_errors++;
		DEBUG("%s: SCCM_TXE=%ld\n", dev->name, chan->stats.tx_errors);
		}

	if (int_events & (SCCM_TX | SCCM_TXE))
		{
		// Queue the task into the immediate bottom half queue
		chan->tx_bh_tq.routine = hdlc_ppp_tx_bh;
		chan->tx_bh_tq.data = (void*)dev;
		queue_task(&chan->tx_bh_tq, &tq_immediate);
		mark_bh(IMMEDIATE_BH);
		}

	// Finished with the ISR
	dev->interrupt = 0;
}


//--------------------------------------------------------------
// hdlc_ppp_tx_bh()
//
// Return:
//	None.
//
// Input Parameters:
//	(I) dev_id - pointer to this driver's device structure
//
// This is one of the bottom half handlers for hdlc_ppp_interrupt().
// It completes the service of TX buffers that have completed.

static void hdlc_ppp_tx_bh(void* dev_id)
{
	struct device* dev = (struct device*)dev_id;
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile cbd_t* bdp;
	int i;

	DEBUG("%s: hdlc_ppp_tx_bh()\n", dev->name);

	// Adjust module count

	// Scan TX bd's and cleanup
	for (i = 0; i < TX_RING_SIZE; ++i)
		{
		bdp = chan->tx_bd[i];

		// Don't touch buffers waiting to be transmitted
		if (bdp->cbd_sc & BD_HDLC_TX_READY)
			continue;

		// Unused bd if its pointer is null
		if (!chan->tx_skbuff[i])
			continue;

		// Free the socket buffer
		dev_kfree_skb(chan->tx_skbuff[i]);
		chan->tx_skbuff[i] = 0;

		// We have at least one TX Buff Descr available now
		dev->tbusy = 0;

		// Accumulate TX statistics
		if (bdp->cbd_sc & BD_HDLC_TX_UN)
			{
			DEBUG("%s: BD_HDLC_TX_UN\n", dev->name);
			chan->stats.tx_fifo_errors++;
			}

		if (bdp->cbd_sc & BD_HDLC_TX_CL)
			{
			DEBUG("%s: BD_HDLC_TX_CL\n", dev->name);
			chan->stats.tx_carrier_errors++;
			}
		}
}


//--------------------------------------------------------------
// hdlc_ppp_rx_bh()
//
// Return:
//	None.
//
// Input Parameters:
//	(I) dev_id - pointer to this driver's device structure
//
// This is one of the bottom half handlers for hdlc_ppp_interrupt().
// It completes the service of RX buffers that have completed.

static void hdlc_ppp_rx_bh(void* dev_id)
{
	struct device* dev = (struct device*)dev_id;
	hdlc_channel_t* chan = (hdlc_channel_t*)dev->priv;
	volatile scc_hdlc_t* hp = (scc_hdlc_t*)dev->mem_start;
	volatile cbd_t* bdp;
	struct sk_buff*	skb;
	struct sk_buff*	newskb;
	ushort pkt_len;
	ushort rx_stat;
	int rxerr;
	int i;

	DEBUG("%s: hdlc_ppp_rx_bh()\n", dev->name);

	while (1)
		{
		// Get the tail of the queue
		i = chan->rx_tail;
		bdp = chan->rx_bd[i];

		if (bdp->cbd_sc & BD_HDLC_RX_EMPTY)
			return; // Nothing more to do

		// Get a socket buffer to replace the one just consumed
		newskb = hdlc_ppp_alloc_skb(hp->genscc.scc_mrblr);
		if (!newskb)
			{
			printk("%s: no rx buffers, dropping packet\n",
				dev->name);
			chan->stats.rx_dropped++;

			// Return here so we don't get out of sync w/cpm
			return;
			}

		// We found a bd with data, bump the tail index
		if (++chan->rx_tail >= RX_RING_SIZE)
			chan->rx_tail = 0;

		// Get the socket data buffer
		skb = chan->rx_skbuff[i];
		chan->rx_skbuff[i] = newskb;
		pkt_len = bdp->cbd_datlen;

		// Get the status of the RX packet
		rx_stat = bdp->cbd_sc;

		// Clear status flags
		bdp->cbd_sc &= ~BD_HDLC_RX_STATS;

		// Set the physical address of the new socket buffer
		bdp->cbd_bufaddr = __pa(newskb->data);

		// Mark ready to receive data
		bdp->cbd_sc |= BD_HDLC_RX_EMPTY;


		// Update statistics
		chan->stats.rx_packets++;
		chan->stats.rx_bytes += pkt_len;
		rxerr = 0;

		if (rx_stat & BD_HDLC_RX_DE)
			{
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_DE\n", dev->name);
			}

		if (rx_stat & BD_HDLC_RX_LG)
			{
			chan->stats.rx_length_errors++;
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_LG\n", dev->name);
			}

		if (rx_stat & BD_HDLC_RX_NO)
			{
			chan->stats.rx_frame_errors++;
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_NO\n", dev->name);
			}

		if (rx_stat & BD_HDLC_RX_AB)
			{
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_AB\n", dev->name);
			}

		if (rx_stat & BD_HDLC_RX_CRC)
			{
			chan->stats.rx_crc_errors++;
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_CRC\n", dev->name);
			}

		if (rx_stat & BD_HDLC_RX_OV)
			{
			chan->stats.rx_over_errors++;
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_OV\n", dev->name);
			}

		if (rx_stat & BD_HDLC_RX_CD)
			{
			chan->stats.rx_missed_errors++;
			rxerr = 1;
			DEBUG("%s: hdlc_ppp_rx_bh(): RX_CD\n", dev->name);
			}

		//chan->stats.rx_fifo_errors

		if (rxerr)
			{
			DEBUG("%s: hdlc_ppp_rx_bh(): rxerr=0x%x\n",
				dev->name, rxerr);
			chan->stats.rx_errors++;
			}

		// Set the length of the received data, minus the CRC
		skb_put(skb, pkt_len - 2);

		// Set the protocol type
		skb->protocol=htons(ETH_P_WAN_PPP);

		// Point to the mac address (first HDLC byte)
		skb->mac.raw = skb->data;

		// This packet is handled by the SYNC PPP driver
		skb->dev = &(chan->pppdev.dev);

		// Send it to the PPP layer
		netif_rx(skb);
		}

}

      reply	other threads:[~2004-05-25  1:48 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2004-05-25  0:39 HDLC driver for MPC8xx SCC HoJin
2004-05-25  1:48 ` leeyang [this message]

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='002501c441fa$579fb3d0$e100a8c0@ycigrnd.ycig.com' \
    --to=leeyang@ycig.com \
    --cc=jhfami@yahoo.co.kr \
    --cc=linuxppc-embedded@lists.linuxppc.org \
    /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).