summaryrefslogtreecommitdiff
path: root/cleopatre/plcdrv/arm/src/linux_drv.c
diff options
context:
space:
mode:
authorsave2008-12-18 10:06:35 +0000
committersave2008-12-18 10:06:35 +0000
commit1369b123d1b9afa9a5ddc70840684da3e451043e (patch)
tree6394a4b4027ab5175de1161169bd232da646ee69 /cleopatre/plcdrv/arm/src/linux_drv.c
parent527d65fbeba78df8a05e12aa3171138d4055d2d1 (diff)
[CLEO][PLCDRV]uppest layer of the plcdrv with fisrt step of the utests
git-svn-id: svn+ssh://pessac/svn/cesar/trunk@3688 017c9cb6-072f-447c-8318-d5b54f68fe89
Diffstat (limited to 'cleopatre/plcdrv/arm/src/linux_drv.c')
-rw-r--r--cleopatre/plcdrv/arm/src/linux_drv.c649
1 files changed, 649 insertions, 0 deletions
diff --git a/cleopatre/plcdrv/arm/src/linux_drv.c b/cleopatre/plcdrv/arm/src/linux_drv.c
new file mode 100644
index 0000000000..13c4283ad1
--- /dev/null
+++ b/cleopatre/plcdrv/arm/src/linux_drv.c
@@ -0,0 +1,649 @@
+/* Cleopatre project {{{
+ *
+ * Copyright (C) 2008 Spidcom
+ *
+ * <<<Licence>>>
+ *
+ * }}} */
+/**
+ * \file linux_drv.c
+ * \brief Linux Driver layer
+ * \ingroup Cleopatre - PlcDrv
+ *
+ * This file content the Linux Driver layer, this layer correspond to the
+ * interface between the driver and Linux (it's a network Linux driver).
+ */
+
+
+#define DRV_NAME "SPC300"
+#define DRV_VERSION "1.0"
+//#define DRV_RELDATE "Dec 05, 2008"
+#define DRV_RELDATE __DATE__
+
+
+#ifndef __UTESTS__
+#include <linux/kernel.h>
+#include <linux/module.h>
+//#include <linux/version.h>
+#include <linux/init.h>
+//#include <linux/errno.h>
+#include <linux/netdevice.h>
+//#include <linux/etherdevice.h>
+//#include <asm/arch/hardware.h>
+//#include <asm/io.h>
+//#include <linux/proc_fs.h>
+//#include <linux/sysctl.h>
+//#include <asm/cacheflush.h>
+//#include <linux/ethtool.h>
+//#include <linux/mii.h>
+//#include <linux/dma-mapping.h>
+//#include <linux/kthread.h>
+
+#include "common.h"
+#include "linux_drv.h"
+#include "processing.h"
+#else
+#include "common.h"
+#include "linux_drv.h"
+#endif
+
+
+MODULE_AUTHOR("SPiDCOM Technologies");
+MODULE_DESCRIPTION("SPC300 PLC driver");
+MODULE_LICENSE("SPiDCOM Technologies 2009");
+
+/** Define Debug/Trace Level */
+#define TRACE(...) printk(DRV_NAME": " __VA_ARGS__)
+//#define TRACE(...)
+
+/** Define default rings size */
+#define DEFAULT_NB_DATA_BUFFERS 5
+#define DEFAULT_NB_MME_BUFFERS 5
+#define DEFAULT_NB_INTERFACE_BUFFERS 5
+
+/** These identify the driver base version */
+static char version[] __devinitdata = DRV_NAME " PLC driver v" DRV_VERSION " (" DRV_RELDATE ")\n";
+
+/** Driver Private datas */
+struct net_priv {
+ uint32_t num_mbx_it;
+ uint32_t num_mbx_it_ack;
+ struct net_device_stats stats;
+};
+
+/** Our global net device */
+static struct net_device *plcdrv_device;
+
+/** Parameters for the module */
+static int rx_data_skb_ring_size = DEFAULT_NB_DATA_BUFFERS;
+static int rx_mme_skb_ring_size = DEFAULT_NB_MME_BUFFERS;
+static int rx_interface_skb_ring_size = DEFAULT_NB_INTERFACE_BUFFERS;
+module_param(rx_data_skb_ring_size, int, 0644);
+MODULE_PARM_DESC(rx_data_skb_ring_size, "Number of Data Ethernet buffers for PLC -> ARM exchanges");
+module_param(rx_mme_skb_ring_size, int, 0644);
+MODULE_PARM_DESC(rx_mme_skb_ring_size, "Number of MME Ethernet buffers for PLC -> ARM exchanges");
+module_param(rx_interface_skb_ring_size, int, 0644);
+MODULE_PARM_DESC(rx_mme_skb_ring_size, "Number of Interface Ethernet buffers for PLC -> ARM exchanges");
+
+/**
+ * Find with sk_buff data field address the sk_buff structure address.
+ * \param skbdata_addr the sk_buff data field address.
+ * \return sk_buff address.
+ */
+static inline uint32_t get_skb_addr(uint32_t skbdata_addr)
+{
+ struct sk_buff *skb = NULL;
+ return (skbdata_addr - ((uint32_t)(&skb->data)));
+}// get_skb_addr
+
+/**
+ * Changed a virtual address to its corresponding physical address
+ * and manage the cache.
+ * \param addr buffer virtual address.
+ * \param len buffer length.
+ * \return buffer physical address.
+ */
+uint32_t prepare_buffer_to_hw(uint32_t addr, unsigned int len)
+{
+ //Invalidate cached areas
+ dma_cache_maint((const void*)addr, len, DMA_BIDIRECTIONAL);
+
+ //Find the corresponding physical addr
+ return (uint32_t)(virt_to_dma(NULL, addr));
+}// prepare_buffer_to_hw
+
+/**
+ * Changed a physical address to its corresponding virtual address
+ * and manage the cache.
+ * \param addr buffer physical address.
+ * \param len buffer length.
+ * \return buffer virtual address.
+ */
+uint32_t prepare_buffer_from_hw(uint32_t addr, unsigned int len)
+{
+ //Invalidate cached areas
+ dma_cache_maint((const void*)addr, len, DMA_BIDIRECTIONAL);
+
+ //Find the corresponding virtual addr
+ return (uint32_t)(dma_to_virt(NULL, addr));
+}// prepare_buffer_from_hw
+
+/**
+ * Alloc a buffer to the pool
+ * and send to the communication layer.
+ * \param type type of buffer to allocate.
+ * \return error code.
+ */
+int alloc_buffer(enum buffer_type type)
+{
+ struct sk_buff *skb;
+ uint32_t pointer;
+ int result;
+
+ //Allocate an sk_buff
+ skb = alloc_skb(PKT_BUF_SZ, GFP_KERNEL | GFP_DMA);
+ if(!skb)
+ {
+ printk(KERN_ERR DRV_NAME": Error allocating RX buffers for %s\n",dev->name);
+ return -ENOMEM;
+ }
+
+ //Find the physical addr of the data part and map it
+ pointer = (uint32_t)dma_map_single(NULL, skb->data, skb->len, DMA_FROM_DEVICE);
+
+ //Send this allocated pointer to lower layer
+ if((result = mailbox_buffer_add((void*)pointer, type)))
+ {
+ kfree_skb(skb);
+ return result;
+ }
+ return 0;
+}// alloc_buffer
+
+/**
+ * Release a buffer from the pool.
+ * \param packet packet pointer.
+ * \param reason freeing reason.
+ * \return error code.
+ */
+int free_buffer(void *packet, enum free_reason reason)
+{
+ struct net_priv *priv = (struct net_priv*)plcdrv_device->priv;
+ struct sk_buff *skb;
+ uint32_t virt_pkt_addr;
+
+ if(packet)
+ {
+ //Find the corresponding virtual addr
+ virt_pkt_addr = dma_to_virt(NULL, (uint32_t)packet);
+
+ //Find the sk_buff associated to this packet
+ skb = (struct sk_buff*)get_skb_addr(virt_pkt_addr);
+
+ //Check the free reason for stats
+ switch(reason)
+ {
+ case RX_DROP: priv->stats.rx_dropped++;
+ break;
+ case TX_DROP: priv->stats.tx_dropped++;
+ break;
+ case TX_COMPLETE: priv->stats.tx_packets++;
+ priv->stats.tx_bytes += skb->len;
+ break;
+ }
+
+ //Unmap the sk_buff
+ dma_unmap_single(NULL, (uint32_t)skb->data, skb->len, DMA_FROM_DEVICE);
+
+ //Free sk_buff
+ kfree_skb(skb);
+ return 0;
+ }
+ else
+ {
+ printk(KERN_ERR DRV_NAME": %s: error freeing a NULL buffer\n", dev->name);
+ return -1;
+ }
+}// free_buffer
+
+/**
+ * Receive a packet.
+ * \param packet packet pointer.
+ * \param length packet length.
+ * \return error code.
+ */
+int plcdrv_rx(void *packet, int length)
+{
+ struct net_priv *priv = (struct net_priv*)plcdrv_device->priv;
+ struct sk_buff *skb;
+ int result;
+
+ TRACE("%s: Receive\n", dev->name);
+
+ if(packet)
+ {
+
+ //Find the sk_buff address
+ skb = (struct sk_buff *)get_skb_addr((uint32_t)packet);
+
+ //Pass data to the linux internal receive level
+ skb->dev = plcdrv_device;
+ skb->protocol = eth_type_trans(skb, plcdrv_device);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ priv->stats.rx_packets++;
+ priv->stats.rx_bytes += length;
+ netif_rx(skb);
+ result = 0;
+ }
+ else
+ {
+ printk(KERN_ERR DRV_NAME": %s: Error Receiving a NULL buffer\n", dev->name);
+ result = -1;
+ }
+
+ TRACE("%s: Receive done\n", dev->name);
+ return result;
+}// plcdrv_rx
+
+/**
+ * Transmit frame procedure.
+ * \param skb frame structure.
+ * \param dev device structure.
+ * \return error code.
+ */
+int plcdrv_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_priv *priv = (struct net_priv*)plcdrv_device->priv;
+ int status;
+
+ TRACE("%s: Transmit\n", dev->name);
+
+ //Send buffer to lower layers
+ status = processing_send((void *)skb->data, skb->len);
+ if(status == NEARLY_FULL)
+ {
+ netif_stop_queue(dev);
+ }
+ else if(status == FULL)
+ {
+ netif_stop_queue(dev);
+ priv->stats.tx_fifo_errors++;
+ kfree_skb(skb);
+ }
+
+ //Handle transmit
+ dev->trans_start = jiffies;
+
+ TRACE("%s: Transmit end\n", dev->name);
+
+ return 0;
+}// plcdrv_tx
+
+/**
+ * Interrupt Handler Receive procedure.
+ * \param irq interrupt number.
+ * \param dev device structure.
+ * \return error code.
+ */
+irqreturn_t plcdrv_it_rx(int irq, void * dev_id)
+{
+ struct net_device *dev = (struct net_device*)dev_id;
+ int result;
+
+ TRACE("%s: Receive\n", dev->name);
+
+ //Just call the lowest layer procedure
+ if(mailbox_receive())
+ result = IRQ_NONE;
+ else
+ result = IRQ_HANDLED;
+ TRACE("%s: Receive end\n", dev->name);
+
+ return result;
+}// plcdrv_it_rx
+
+/**
+ * Finish the transmit frame procedure.
+ * \param irq interrupt number.
+ * \param dev device structure.
+ */
+irqreturn_t plcdrv_it_txdone(int irq, void * dev_id)
+{
+ struct net_device *dev = (struct net_device*)dev_id;
+
+ TRACE("%s: Transmit Done\n", dev->name);
+ if(netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+ TRACE("%s: Transmit Done end\n", dev->name);
+
+ return IRQ_HANDLED;
+}// plcdrv_it_txdone
+
+/**
+ * Read packet status from the device.
+ * \param dev device structure.
+ * \return the device stats.
+ */
+struct net_device_stats *plcdrv_stats(struct net_device *dev)
+{
+ struct net_priv *priv = NULL;
+
+ //Check pointers
+ if(dev == NULL)
+ return NULL;
+
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return NULL;
+
+ return &priv->stats;
+}// plcdrv_stats
+
+/**
+ * Change the MTU.
+ * \param dev device structure.
+ * \param new_mtu the new mtu value.
+ * \return error code.
+ */
+int plcdrv_change_mtu(struct net_device *dev, int new_mtu)
+{
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+
+ //Check arguments
+ if(new_mtu < 64 || new_mtu > 1508)
+ return -EINVAL;
+ else
+ {
+ dev->mtu = new_mtu;
+ return 0;
+ }
+}// plcdrv_change_mtu
+
+/**
+ * Change the MAC address.
+ * \param dev device structure.
+ * \param p mac addr source.
+ * \return error code.
+ */
+int plcdrv_set_mac_address(struct net_device *dev, void *p)
+{
+ struct sockaddr *addr = p;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ if(p == NULL)
+ return -1;
+
+ TRACE("%s: set_mac_address\n", dev->name);
+
+ //Store the new address for Linux
+ memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
+
+ //TODO:Store the new address in NVRAM
+
+ TRACE("%s: set_mac_address end\n", dev->name);
+
+ return 0;
+}// plcdrv_set_mac_address
+
+/**
+ * User control device interface.
+ * \param dev device structure.
+ * \param ifr user exchange structure.
+ * \param cmd command to execute.
+ * \return error code.
+ */
+int plcdrv_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ //Check pointers
+ if(ifr == NULL)
+ return -1;
+ if(dev == NULL)
+ return -1;
+
+ TRACE("%s: ioctl\n", dev->name);
+
+ //Find the command
+ switch(cmd)
+ {
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ TRACE("%s: ioctl end\n", dev->name);
+
+ return 0;
+}// plcdrv_ioctl
+
+/**
+ * Initialize the device.
+ * \param dev device structure.
+ * \return error code.
+ */
+int plcdrv_open(struct net_device *dev)
+{
+ struct net_priv *priv = NULL;
+ int i;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ TRACE("%s: open\n", dev->name);
+
+ //Start lower Layers
+ processing_init();
+
+ //Allocate RX buffer pool to give to CESAR
+ for(i=0 ; i<rx_data_skb_ring_size ; i++)
+ {
+ if(alloc_buffer(DATA))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: error creating DATA buffer pool\n", dev->name);
+ return -ENOMEM;
+ }
+ }
+ for(i=0 ; i<rx_mme_skb_ring_size ; i++)
+ {
+ if(alloc_buffer(MME))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: error creating MME buffer pool\n", dev->name);
+ return -ENOMEM;
+ }
+ }
+ for(i=0 ; i<rx_interface_skb_ring_size ; i++)
+ {
+ if(alloc_buffer(INTERFACE))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: error creating INTERFACE buffer pool\n", dev->name);
+ return -ENOMEM;
+ }
+ }
+
+ //Prepare Linux as link up
+ netif_carrier_on(dev);
+ netif_start_queue(dev);
+
+ //Request Receive IRQ
+ if(request_irq(priv->num_mbx_it, plcdrv_it_rx, 0, dev->name, dev) != 0)
+ {
+ printk(KERN_ERR DRV_NAME ": %s - interrupt %d request fail\n", dev->name, dev->irq);
+ return -ENODEV;
+ }
+
+ //Request Transmit Acknowledge IRQ
+ if(request_irq(priv->num_mbx_it_ack, plcdrv_it_txdone, 0, dev->name, dev) != 0)
+ {
+ printk(KERN_ERR DRV_NAME ": %s - interrupt %d request fail\n", dev->name, dev->irq);
+ return -ENODEV;
+ }
+
+ TRACE("%s: open end\n", dev->name);
+
+ return 0;
+}// plcdrv_open
+
+/**
+ * Uninitialize the device.
+ * \param dev device structure.
+ * \return error code.
+ */
+int plcdrv_stop(struct net_device *dev)
+{
+ struct net_priv *priv = NULL;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ TRACE("%s: stop\n", dev->name);
+
+ //Disable transmitter
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+
+ //Stop lower layers
+ processing_uninit();
+
+ //Disconnect from IRQ
+ free_irq(priv->num_mbx_it, dev);
+ free_irq(priv->num_mbx_it_ack, dev);
+
+ //TODO:Freeing all buffers
+ //TODO: send a message to processing layer or communication layer
+ //that we want to shutdown le plc driver
+ //Wait all allocated sk_buff become free
+ //(a sk_buff become free when we receive a send_done procedure)
+
+ TRACE("%s: stop end\n", dev->name);
+
+ return 0;
+}// plcdrv_stop
+
+/**
+ * Initialise the network device.
+ * \param dev device structure.
+ * \return error code.
+ */
+int plcdrv_init(struct net_device *dev)
+{
+ struct net_priv *priv = NULL;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ TRACE("%s: init\n", dev->name);
+
+ //Set IP base addresses
+ dev->base_addr = (unsigned int)ioremap(MBX_BASE_ADDR, 6);
+
+ //Set Interrupts numbers
+ dev->irq = INT_MBX; //do not use dev->irq because there more than one IT given by the hardware
+ priv->num_mbx_it = INT_MBX;
+ priv->num_mbx_it_ack = INT_MBX_ACK;
+
+ //TODO:May be Attach hardware layer addresses + start leon....
+
+ //Initialise device functions
+ ether_setup(dev);
+ dev->open = plcdrv_open;
+ dev->stop = plcdrv_stop;
+ dev->do_ioctl = plcdrv_ioctl;
+ dev->set_mac_address = plcdrv_set_mac_address;
+ dev->hard_start_xmit = plcdrv_tx;
+ dev->get_stats = plcdrv_stats;
+/* dev->tx_timeout = plcdrv_tx_timeout; */
+/* dev->watchdog_timeo = TX_TIMEOUT; */
+ dev->change_mtu = plcdrv_change_mtu;
+
+ //TODO:Setup MAC address for Linux (stored in NVRAM)
+ dev->dev_addr[0] = 0x00;
+ dev->dev_addr[1] = 0x11;
+ dev->dev_addr[2] = 0x22;
+ dev->dev_addr[3] = 0x33;
+ dev->dev_addr[4] = 0x44;
+ dev->dev_addr[5] = 0x55;
+
+ TRACE("%s: init end\n", dev->name);
+
+ return 0;
+}// plcdrv_init
+
+/**
+ * Initialise the module.
+ * \return error code.
+ */
+int __init plcdrv_module_init(void)
+{
+ int result;
+ struct net_device *dev;
+
+ printk("%s", version);
+
+ //Allocate device memory
+ dev = alloc_netdev(sizeof(struct net_priv), "plc%d", ether_setup);
+ if((dev == NULL) || (dev->priv == NULL))
+ result = -ENOMEM;
+
+ //Proceed the init driver
+ dev->init = plcdrv_init;
+
+ //Register net device
+ result = register_netdev(dev);
+ if(result < 0)
+ {
+ printk(KERN_ERR DRV_NAME": Error %i registering %s\n", result, dev->name);
+ kfree(dev->priv);
+ free_netdev(dev);
+ }
+ else
+ {
+ plcdrv_device = dev;
+ }
+
+ return result;
+}// plcdrv_module_init
+
+/**
+ * Uninitialise the module.
+ */
+void __exit plcdrv_module_exit(void)
+{
+ struct net_priv *priv;
+
+ if(plcdrv_device)
+ {
+ //Unmap IP address
+ if(plcdrv_device->base_addr)
+ iounmap((void*)plcdrv_device->base_addr);
+
+ //Freeing private field of the net device structure
+ priv = plcdrv_device->priv;
+ if(priv)
+ kfree(priv);
+
+ //Unregister net device
+ unregister_netdev(plcdrv_device);
+
+ //Freeing network device
+ free_netdev(plcdrv_device);
+ }
+
+}// plcdrv_module_exit
+
+module_init(plcdrv_init_module);
+module_exit(plcdrv_exit_module);
+