/*
* ed_device.c embeded software project(1) device file.
*
* Copyright (C) 2003 Li Suke,Software School of Peking University.
*
* The data flow of the devices is:
*
* neted
* _______|___________
* | |
* | |
* ed_rec ed_tx
* (recieve) (transmit)
*
* neted: pseodu network device
* ed_rec: character device
* ed_tx: character device
* You can modify and distribute this source code freely.
*
*/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/config.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#ifdef LINUX_24
#include <linux/mm.h>
#else
#include <linux/malloc.h>
#endif
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/in.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ip.h>
#include <linux/skbuff.h>
#include <linux/ioctl.h>
#ifdef LINUX_20
#include <linux/if_ether.h>
#endif
#include <asm/uaccess.h>
#include "ed_device.h"
#include "ed_ioctl.h"
MODULE_AUTHOR("Li Suke");
/* We must define the htons() function here, for the kernel has no
* this API if you do not make source code dep
*/
#define htons(x) ((x>>8) | (x<<8))
char ed_names[16];
struct ed_device ed[2];
#ifdef LINUX_24
struct net_device ednet_dev;
#else
struct device ednet_dev;
#endif
#ifdef LINUX_24
static int timeout = ED_TIMEOUT;
#endif
#ifdef LINUX_24
void ednet_rx(struct net_device *dev,int len,unsigned char *buf);
#else
void ednet_rx(struct device *dev,int len,unsigned char *buf);
#endif
#ifdef LINUX_24
void ednet_tx_timeout (struct net_device *dev);
#else
void ednet_tx_timeout (struct device *dev);
#endif
/* Initialize the ed_rec and ed_tx device,the two devices
are allocate the initial buffer to store the incoming and
outgoing data. If the TCP/IP handshake need change the
MTU,we must reallocte the buffer using the new MTU value.
*/
static int device_init(){
int i;
int err;
err = -ENOBUFS;
strcpy(ed[ED_REC_DEVICE].name, ED_REC_DEVICE_NAME);
strcpy(ed[ED_TX_DEVICE].name, ED_TX_DEVICE_NAME);
for (i = 0 ;i < 2; i++ )
{
ed[i].buffer_size = BUFFER_SIZE;
ed[i].buffer = kmalloc(ed[i].buffer_size + 4 , GFP_KERNEL);
ed[i].magic = ED_MAGIC;
ed[i].mtu = ED_MTU;
ed[i].busy = 0;
#ifdef LINUX_24
init_waitqueue_head(&ed[i].rwait);
#endif
if (ed[i].buffer == NULL)
goto err_exit;
spin_lock_init(&ed[i].lock);
}
err = 0;
return err;
err_exit:
printk("There is no enongh memory for buffer allocation. \n");
return err;
}
static int ed_realloc(int new_mtu){
int err;
int i;
err = -ENOBUFS;
char *local_buffer[2];
int size;
for (i=0;i<2;i++){
local_buffer[i] = kmalloc(new_mtu + 4,GFP_KERNEL);
#ifdef LINUX_20
if(new_mtu >= ed[i].buffer_size)
size = new_mtu;
else
size = ed[i].buffer_size;
#else
size = min(new_mtu,ed[i].buffer_size);
#endif
memcpy(local_buffer[i],ed[i].buffer,size);
kfree(ed[i].buffer);
ed[i].buffer = kmalloc(new_mtu + 4,GFP_KERNEL);
if( ed[i].buffer < 0){
printk("Can not realloc the buffer from kernel when change mtu.\n");
return err;
}
}
return 0;
}
/* Open the two character devices,and let the ed_device's private pointer
* point to the file struct */
static int device_open(struct inode *inode,struct file *file)
{
int Device_Major;
struct ed_device *edp;
Device_Major = inode->i_rdev >> 8;
#ifdef _DEBUG
printk("Get the Device Major Number is %d\n",Device_Major);
#endif
if (Device_Major == MAJOR_NUM_REC )
{
file->private_data = &ed[ED_REC_DEVICE];
ed[ED_REC_DEVICE].file = file;
}
else
if (Device_Major == MAJOR_NUM_TX){
file->private_data = &ed[ED_TX_DEVICE];
ed[ED_TX_DEVICE].file = file;
}
else
return -NODEV;
edp = (struct ed_device *)file->private_data;
if(edp->busy != 0){
printk("The device is open!\n");
return -EBUSY;
}
edp->busy++;
return 0;
}
/* release the devices */
int device_release(struct inode *inode,struct file *file)
{
struct ed_device *edp;
edp = (struct ed_device *)file->private_data;
edp->busy = 0;
return 0;
}
/* read data from ed_tx device */
ssize_t device_read(struct file *file,char *buffer,size_t length, loff_t *offset)
{
#ifdef _DEBUG
int i;
#endif
struct ed_device *edp;
edp = (struct ed_device *)file->private_data;
#ifdef LINUX_24
DECLARE_WAITQUEUE(wait,current);
add_wait_queue(&edp->rwait,&wait);
for(;;){
set_current_state(TASK_INTERRUPTIBLE);
if ( file->f_flags & O_NONBLOCK)
break;
if ( edp->tx_len > 0)
break;
if ( signal_pending(current))
break;
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&edp->rwait,&wait);
#endif
spin_lock(&edp->lock);
if(edp->tx_len == 0) {
spin_unlock(&edp->lock);
return 0;
}else
{
copy_to_user(buffer,edp->buffer,edp->tx_len);
memset(edp->buffer,0,edp->buffer_size);
#ifdef _DEBUG
printk("\n read data from ed_tx \n");
for(i=0;i<edp->tx_len;i++)
printk(" %02x",edp->buffer[i]&0xff);
printk("\n");
#endif
length = edp->tx_len;
edp->tx_len = 0;
}
spin_unlock(&edp->lock);
return length;
}
/* This function is called by ednet device to write the network data
* into the ed_tx character device.
*/
ssize_t kernel_write(const char *buffer,size_t length,int buffer_size)
{
if(length > buffer_size )
length = buffer_size;
memset(ed[ED_TX_DEVICE].buffer,0,buffer_size);
memcpy(ed[ED_TX_DEVICE].buffer,buffer,buffer_size);
ed[ED_TX_DEVICE].tx_len = length;
#ifdef LINUX_24
wake_up_interruptible(&ed[ED_TX_DEVICE].rwait);
#endif
return length;
}
/* Device write is called by server program, to put the user space
* network data into ed_rec device.
*/
ssize_t device_write(struct file *file,const char *buffer, size_t length,loff_t *offset)
{
#ifdef _DEBUG
int i;
#endif
struct ed_device *edp;
edp = (struct ed_device *)file->private_data;
spin_lock(&ed[ED_REC_DEVICE].lock);
if(length > edp->buffer_size)
length = edp->buffer_size;
copy_from_user( ed[ED_REC_DEVICE].buffer,buffer, length);
ednet_rx(&ednet_dev,length,ed[ED_REC_DEVICE].buffer);
#ifdef _DEBUG
printk("\nNetwork Device Recieve buffer:\n");
for(i =0;i< length;i++)
printk(" %02x",ed[ED_REC_DEVICE].buffer[i]&0xff);
printk("\n");
#endif
spin_unlock(&ed[ED_REC_DEVICE].lock);
return length;
}
int device_ioctl(struct inode *inode,
struct file *file,
unsigned int ioctl_num,
unsigned long ioctl_param){
struct ed_device *edp;
edp = (struct ed_device *)file->private_data;
switch(ioctl_num)
{
case IOCTL_SET_BUSY:
edp->busy = ioctl_param;
break;
}
return 0;
}
/*
* All the ednet_* functions are for the ednet pseudo network device ednet.
* ednet_open and ednet_release are the two functions which open and release
* the device.
*/
#ifdef LINUX_20
int ednet_open(struct device *dev)
#else
int ednet_open(struct net_device *dev)
#endif
{
MOD_INC_USE_COUNT;
/* Assign the hardware pseudo network hardware address,
* the MAC address's f