summaryrefslogtreecommitdiff
path: root/cleopatre/devkit/plcdrv/src/linux_drv.c
diff options
context:
space:
mode:
Diffstat (limited to 'cleopatre/devkit/plcdrv/src/linux_drv.c')
-rw-r--r--cleopatre/devkit/plcdrv/src/linux_drv.c2042
1 files changed, 2042 insertions, 0 deletions
diff --git a/cleopatre/devkit/plcdrv/src/linux_drv.c b/cleopatre/devkit/plcdrv/src/linux_drv.c
new file mode 100644
index 0000000000..d76fa729f0
--- /dev/null
+++ b/cleopatre/devkit/plcdrv/src/linux_drv.c
@@ -0,0 +1,2042 @@
+/* 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_LAYER "PLC "
+#define DRV_RELDATE __DATE__ " " __TIME__
+
+
+#ifndef __UTESTS__
+# include <linux/types.h>
+# include <linux/kernel.h>
+# include <linux/module.h>
+# include <linux/afe.h>
+# include <linux/init.h>
+# include <linux/errno.h>
+# include <linux/netdevice.h>
+# include <linux/etherdevice.h>
+# include <asm/semaphore.h>
+# include <linux/kdev_t.h>
+# include <linux/cdev.h>
+# include <linux/fs.h>
+# include <linux/proc_fs.h>
+# include <asm/uaccess.h>
+# include <linux/list.h>
+# include <linux/netlink.h>
+# include <linux/poll.h>
+# include <linux/wait.h>
+# include "net/seq_check.h"
+
+# include <asm/arch/nvram.h>
+# include <asm/arch/hardware/spi.h>
+# include <asm/arch/hardware/regbank.h>
+# include <asm-arm/arch-spc300/ioctl.h>
+#else
+# include <linux/types.h>
+# include <linux/kernel.h>
+# include <linux/init.h>
+# include <linux/mutex.h>
+# include <linux/list.h>
+# include <linux/wait.h>
+# include <linux/afe.h>
+# include <linux/fs.h>
+# include <linux/poll.h>
+# include <linux/bitops.h> /* non present au-dessus, inclus par qqn ? */
+# include <linux/gfp.h> /* non present au-dessus, inclus par qqn ? */
+# include <linux/skbuff.h>
+# include <linux/netlink.h>
+# include <linux/interrupt.h>
+# include <linux/irqreturn.h>
+# include <linux/module.h>
+# include <linux/moduleparam.h>
+# include <linux/kdev_t.h>
+# include <linux/cdev.h>
+# include <linux/if_ether.h>
+# include <linux/errno.h>
+# include <linux/sched.h>
+# include <linux/dma-mapping.h>
+# include <net/net_namespace.h>
+# include <net/seq_check.h>
+
+# include <asm/arch/nvram.h>
+# include <asm/arch/hardware/regbank.h>
+
+/* TODO : move this ! */
+unsigned char plccode[124];
+#define VIRT_PLCCODE_BASE (&plccode[0])
+#define jiffies 1234
+struct net init_net;
+spc300_nvram_t spc300_nvram;
+
+#endif
+
+#include "boot_params.h"
+#include "common.h"
+#include "linux_drv.h"
+#include "processing.h"
+#include "mailbox.h"
+#include "hal.h"
+#include "registers.h"
+
+MODULE_AUTHOR("SPiDCOM Technologies");
+MODULE_DESCRIPTION("SPC300 PLC driver");
+MODULE_LICENSE("SPiDCOM Technologies 2009");
+
+/** Define Debug/Trace Level */
+#define TRACE(...) if(test_bit(TRACE_LINUX, (const volatile unsigned long*)&trace)) printk(KERN_INFO DRV_NAME": "DRV_LAYER": " __VA_ARGS__)
+#define PRINTPKT(a,b,c) if(test_bit(TRACE_PACKET, (const volatile unsigned long*)&trace)) print_packet(a,b,c)
+
+/** Define DSU trace modes */
+#define DSU_TRACE_NONE 0
+#define DSU_TRACE_PROC 1
+#define DSU_TRACE_AHB 2
+#define DSU_TRACE_ALL 3
+
+#define MSEC_PER_JIFFY (1000/HZ) //aligned on HZ, which corresponds to 10ms
+
+/** Define default numbers of buffers */
+#define DEFAULT_NB_DATA_BUFFERS 97
+#define DEFAULT_NB_MME_BUFFERS 1
+#define DEFAULT_NB_INTERFACE_BUFFERS 2
+
+/** Fake address used to detect that a leon_start_addr was provided as a
+ * module param. */
+#define INVALID_LEON_START_ADDR 1
+
+/** Max allowed TX message in the same time */
+#define MBX_TX_POOL (L2A_RING_SIZE / MAX_MSG_SIZE / 2)
+
+/** These identify the driver base version */
+static char version[] __devinitdata = DRV_NAME " PLC driver v" DRV_VERSION " (" DRV_RELDATE ")\n";
+
+/** Structure used with Linux list to manage the sk_buff in used */
+struct skb_addr_list {
+ struct list_head list;
+ uint32_t *pkt_addr;
+ struct sk_buff *skb;
+};
+
+/** Our global netlink mutex */
+static DEFINE_MUTEX(plcdrv_nl_mutex);
+
+/** Our global net device */
+static struct net_device *plcdrv_device;
+
+/** Our global Major number */
+static dev_t number;
+/** Our global character device */
+static struct cdev plcdrv_char_dev;
+/** Our trace character device. */
+static struct cdev trace_cdev;
+
+/** Our plc directory in procfs. */
+static struct proc_dir_entry *proc_plc_dir;
+
+/** Our mutex between firmware download and network driver start-up */
+static uint8_t write_called = 0;
+
+/** Parameters for the module */
+static int nb_rx_data_buffers = DEFAULT_NB_DATA_BUFFERS;
+static int nb_rx_mme_buffers = DEFAULT_NB_MME_BUFFERS;
+static int nb_rx_interface_buffers = DEFAULT_NB_INTERFACE_BUFFERS;
+static uint32_t leon_start_addr = INVALID_LEON_START_ADDR;
+static uint32_t dsu_ctrl = (LEON_DSU_BZ | LEON_DSU_BD | LEON_DSU_BW | LEON_DSU_BE | LEON_DSU_TE);
+static uint32_t dsu_trace = DSU_TRACE_PROC;
+static int debug = 0;
+uint32_t trace = 0;
+
+/** For debug dump buffer. */
+int debug_dump_buffer_length_received = -1;
+bool debug_dump_waiting_for_buffer = false;
+const uint debug_dump_buffer_length = 2048;
+DECLARE_WAIT_QUEUE_HEAD(debug_dump_wait_queue);
+bool debug_dump_opened = false;
+
+module_param(nb_rx_data_buffers, int, 0644);
+MODULE_PARM_DESC(nb_rx_data_buffers, "Number of Data Ethernet buffers for PLC -> ARM exchanges");
+module_param(nb_rx_mme_buffers, int, 0644);
+MODULE_PARM_DESC(nb_rx_mme_buffers, "Number of MME Ethernet buffers for PLC -> ARM exchanges");
+module_param(nb_rx_interface_buffers, int, 0644);
+MODULE_PARM_DESC(nb_rx_interface_buffers, "Number of Interface Ethernet buffers for PLC -> ARM exchanges");
+module_param(leon_start_addr, uint, 0644);
+MODULE_PARM_DESC(leon_start_addr, "PLC code start address");
+module_param_string(boot_params, custom_boot_params,
+ sizeof(custom_boot_params), 0644);
+MODULE_PARM_DESC(custom_boot_params, "PLC Boot Parameters");
+module_param(dsu_ctrl, uint, 0644);
+MODULE_PARM_DESC(dsu_ctrl, "Configure the PLC Processor debugger");
+module_param(dsu_trace, uint, 0644);
+MODULE_PARM_DESC(dsu_trace, "Configure the PLC Processor trace (0=no ; 1=proc ; 2=AHB ; 3=all)");
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Let's the user start PLC Processor by debugger");
+module_param(trace, uint, 0644);
+
+void plcdrv_uninit(struct net_device *dev);
+
+
+static const unsigned char __hexdigits[] = "0123456789ABCDEF";
+static void sprintf_hex(unsigned char * str, const unsigned char * ptr, int len, unsigned char delim)
+{
+ int i, j=0;
+ for(i=0; i<len; i++){
+ if(i)str[j++]=delim;
+ str[j++]=__hexdigits[ptr[i]>>4];
+ str[j++]=__hexdigits[ptr[i]&0x0F];
+ }
+ str[j] = 0;
+}
+static void print_packet(const char * prefix, struct sk_buff * skb, int len)
+{
+ struct ethhdr * h;
+ unsigned char src[20], dst[20], body[50];
+ int l;
+
+ h = (struct ethhdr *)skb->data;
+ l = len - 14 > 16 ? 16 : len - 14;
+ sprintf_hex(src, &h->h_source[0], 6, ':');
+ sprintf_hex(dst, &h->h_dest[0], 6, ':');
+ sprintf_hex(body, ((unsigned char *)skb->data)+14, l, ' ');
+
+ printk(KERN_INFO "%s: len=%-4d proto=0x%04X src=%s dst=%s\n", prefix, len, be16_to_cpu(h->h_proto), src, dst);
+ printk(KERN_INFO " body=%s\n", body);
+}
+
+/**
+ * Read Version number by /proc.
+ *
+ * \param file file structure.
+ * \param buffer string pointer given by user.
+ * \param start string pointer begin.
+ * \param offset offset value.
+ * \param count count parameter.
+ * \param eof end of file.
+ * \param data network device structure.
+ * \return new pointer position.
+ */
+static int plcdrv_readproc_version(char *buf, char **start, off_t offset, int count, int *eof, void *data)
+{
+ struct net_device *dev = (struct net_device*)data;
+ struct net_priv *priv;
+ char *p;
+
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ p = buf;
+ p += sprintf(p, "%s: %s\n", "PLC Driver", DRV_VERSION);
+ p += sprintf(p, "%s: %s\n", "PLC Firmware", priv->version);
+ *eof = 1;
+ return p-buf+1;
+}
+
+/**
+ * Configure AFE with the default configuration.
+ */
+static int init_afe(void)
+{
+ int ret;
+
+#ifdef CONFIG_MACH_ARIZONA
+ ret = afe_write_reg(0x03, 0x04)
+ || afe_write_reg(0x04, 0x35)
+ || afe_write_reg(0x06, 0x44)
+ || afe_write_reg(0x0A, 0x7F)
+ || afe_write_reg(0x0C, 0x43)
+ || afe_write_reg(0x0D, 0x01)
+ || afe_write_reg(0x0E, 0x80);
+#else
+ ret = afe_write_reg(0x04, 0x16)
+ || afe_write_reg(0x05, 0x80)
+ || afe_write_reg(0x07, 0x20)
+ || afe_write_reg(0x0A, 0x7F) // TODO: check diff values 9865 / 9867
+ || afe_write_reg(0x0B, 0x20)
+ || afe_write_reg(0x0C, 0x51) // TODO: check diff values 9865 / 9867
+ || afe_write_reg(0x0D, 0x01)
+ || afe_write_reg(0x0E, 0x08)
+ || afe_write_reg(0x03, 0xF8);
+#endif
+
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * Read plc stats
+ *
+ * \param file file structure.
+ * \param buffer string pointer given by user.
+ * \param start string pointer begin.
+ * \param offset offset value.
+ * \param count count parameter.
+ * \param eof end of file.
+ * \param data network device structure.
+ * \return new pointer position.
+ */
+static int plcdrv_readproc_plc_stats(char *buf, char **start, off_t offset, int count, int *eof, void *data)
+{
+ struct net_device *dev = (struct net_device*)data;
+ struct net_priv *priv;
+ char *p;
+
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ p = buf;
+ p += sprintf (p, "Tx pool: %u\n", priv->plc_stats.tx_pool);
+ p += sprintf (p, "Rx pool: %u\n", priv->plc_stats.rx_pool);
+ p += sprintf (p, "L2A max length: %u\n", priv->halctx->L2A_max_length);
+ *eof = 1;
+ return p-buf+1;
+}
+
+/**
+ * Set/Unset Reset the Leon processor.
+ *
+ * \param activate 1 to activate reset.
+ * \return error code.
+ */
+int plcdrv_reset_leon(int activate)
+{
+ if(activate)
+ {
+ RB_RST_GROUP_VA |= RST_LEONSS;
+ if(!(RB_RST_GROUP_VA & RST_LEONSS))
+ return -1;
+
+ RB_RST_MODULE_VA |= RST_LCPU;
+ if(!(RB_RST_MODULE_VA & RST_LCPU))
+ return -1;
+ }
+ else
+ {
+ RB_RST_GROUP_VA &= ~RST_LEONSS;
+ if(RB_RST_GROUP_VA & RST_LEONSS)
+ return -1;
+
+ RB_RST_MODULE_VA &= ~RST_LCPU;
+ if(RB_RST_MODULE_VA & RST_LCPU)
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Set/Unset Reset the DSP.
+ *
+ * \param activate 1 to activate reset.
+ * \return error code.
+ */
+int plcdrv_reset_dsp(int activate)
+{
+ if(activate)
+ {
+ RB_RST_GROUP_VA |= RST_DSP;
+ if(!(RB_RST_GROUP_VA & RST_DSP))
+ return -1;
+ }
+ else
+ {
+ RB_RST_GROUP_VA &= ~RST_DSP;
+ if(RB_RST_GROUP_VA & RST_DSP)
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Launch the Leon processor.
+ *
+ * \return error code.
+ */
+int plcdrv_launch_leon(void)
+{
+ uint32_t *leon_dsu_ptr;
+ uint32_t *leon_wd_ptr;
+ uint32_t *leon_trace_ptr;
+ int result=0;
+
+ //Prepare Leon registers accesses
+ leon_dsu_ptr = (uint32_t*)ioremap(LEON_DSU_CTRL_BASE_ADDR, 1);
+ leon_trace_ptr = (uint32_t*)ioremap(LEON_TRACE_CTRL_BASE_ADDR, 1);
+ leon_wd_ptr = (uint32_t*)ioremap(LEON_WD_BASE_ADDR, 1);
+
+ //Set trace mode for leon
+ switch(dsu_trace)
+ {
+ case DSU_TRACE_NONE:
+ dsu_ctrl &= ~LEON_DSU_TE;
+ *leon_trace_ptr &= ~(LEON_TRACE_PROC_EN | LEON_TRACE_AHB_EN);
+ break;
+ case DSU_TRACE_ALL:
+ dsu_ctrl |= LEON_DSU_TE;
+ *leon_trace_ptr |= (LEON_TRACE_PROC_EN | LEON_TRACE_AHB_EN);
+ break;
+ case DSU_TRACE_AHB:
+ dsu_ctrl |= LEON_DSU_TE;
+ *leon_trace_ptr &= ~(LEON_TRACE_PROC_EN);
+ *leon_trace_ptr |= (LEON_TRACE_AHB_EN);
+ break;
+ case DSU_TRACE_PROC:
+ default:
+ dsu_ctrl |= LEON_DSU_TE;
+ *leon_trace_ptr &= ~(LEON_TRACE_AHB_EN);
+ *leon_trace_ptr |= (LEON_TRACE_PROC_EN);
+ }
+
+ //Refresh watchdog before running (for 10 seconds)
+ *leon_wd_ptr = LEON_WD_REFRESH(10);
+
+ //Configure DSU + Resume Leon processor execution
+ *leon_dsu_ptr = (dsu_ctrl & ~(LEON_DSU_BN | LEON_DSU_FT));
+
+ //Check if processor is running
+ if((*leon_dsu_ptr) & LEON_DSU_BN)
+ {
+ result = -1;
+ }
+
+ //Release reset Leon register
+ iounmap((void*)leon_dsu_ptr);
+ iounmap((void*)leon_wd_ptr);
+ iounmap((void*)leon_trace_ptr);
+
+ return result;
+}
+
+/**
+ * Find the start and size of the ROM info area.
+ *
+ * \param file_end end of the file.
+ * \param[out] rom_info_start start of the ROM info area.
+ * \param[out] rom_info_size size of the ROM info area.
+ */
+void plcdrv_find_rom_info(uint8_t *file_end, uint8_t **rom_info_start,
+ size_t *rom_info_size)
+{
+ uint8_t *infos;
+ uint32_t infos_limit;
+ int infos_size;
+
+ BUG_ON(file_end == NULL);
+ BUG_ON(rom_info_start == NULL);
+ BUG_ON(rom_info_size == NULL);
+
+ infos = file_end;
+ infos_limit = (uint32_t)infos - ROM_INFO_MAX_SIZE;
+ infos_size = 0;
+
+ //No informations area
+ if(*infos != ROM_INFO_DELIMITER)
+ {
+ goto not_found;
+ }
+
+ //Find start of informations area
+ //We are at the end. We go back until we find a delimiter of the beginning
+ //of the area.
+ while(((uint32_t)infos > infos_limit-1)
+ && ((*infos != ROM_INFO_DELIMITER)
+ || (*(infos-1) != ROM_INFO_DELIMITER)))
+ {
+ infos--;
+ infos_size++;
+ }
+
+ //Informations not found
+ if((uint32_t)infos <= infos_limit)
+ {
+ goto not_found;
+ }
+
+ //There may be more than one delimiter at the beginning of the area.
+ //As we need to kwow the exact start of the area,
+ //we go up until we see all the delimiters.
+ BUG_ON (*infos != ROM_INFO_DELIMITER);
+ while(((uint32_t)infos > infos_limit-1) && (*infos == ROM_INFO_DELIMITER))
+ {
+ infos--;
+ infos_size++;
+ }
+
+ *rom_info_start = infos + 1;
+ *rom_info_size = infos_size;
+
+ return;
+
+not_found:
+ *rom_info_start = NULL;
+ *rom_info_size = 0;
+
+ return;
+}
+
+/**
+ * Get plc.rom version number.
+ *
+ * \param rom_info_start start of the ROM info area.
+ * \param rom_info_size size of the ROM info area.
+ * \param[out] version version result buffer.
+ */
+void plcdrv_get_rom_version(uint8_t *rom_info_start, size_t rom_info_size,
+ uint8_t *version)
+{
+ uint8_t *infos, *p;
+
+ BUG_ON(version == NULL);
+
+ if ((rom_info_start == NULL) || (rom_info_size == 0))
+ goto unknown;
+
+ infos = rom_info_start;
+
+ //Skip delimiters at the beginning of the info area
+ while ((infos < (rom_info_start + rom_info_size)
+ && (*infos == ROM_INFO_DELIMITER)))
+ {
+ infos++;
+ }
+
+ if (infos >= (rom_info_start + rom_info_size))
+ {
+ goto unknown;
+ }
+
+ //Split informations, format is "key: value\n"
+ for (p = infos ; p < (rom_info_start + rom_info_size) ; p++)
+ {
+ if((*p == ROM_INFO_DELIMITER) || (*p == ROM_INFO_KEY_DELIMITER))
+ *p = '\0';
+ }
+
+ //Find version key
+ while (strcasecmp(infos, ROM_VERSION_KEY))
+ {
+ infos += strlen(infos)+1; //to skip the key
+ infos += strlen(infos)+1; //to skip the associated value
+ }
+
+ //Skip version key
+ infos += strlen(infos)+1;
+
+ //Copy version value without first space
+ strncpy(version, infos+1, ROM_VERSION_SIZE);
+
+ return;
+
+unknown:
+ strcpy(version, "Unknown");
+
+ return;
+}
+
+/**
+ * Open the character device.
+ *
+ * \param inp inode structure.
+ * \param filp file structure.
+ * \return error code.
+ */
+int plcdrv_char_open(struct inode *inp, struct file *filp)
+{
+ filp->private_data = plcdrv_device;
+ write_called = 0;
+ return 0;
+}
+
+/**
+ * Close the character device.
+ *
+ * \param inp inode structure.
+ * \param filp file structure.
+ * \return error code.
+ */
+int plcdrv_char_close(struct inode *inp, struct file *filp)
+{
+ struct net_device *dev = filp->private_data;
+ struct net_priv *priv;
+
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ if(write_called)
+ {
+ uint8_t *file_end = (uint8_t *)VIRT_PLCCODE_BASE + filp->f_pos - 1;
+ uint8_t *rom_info_start;
+ size_t rom_info_size;
+
+ plcdrv_find_rom_info(file_end, &rom_info_start, &rom_info_size);
+
+ //Get firmware version number
+ plcdrv_get_rom_version(rom_info_start, rom_info_size, priv->version);
+
+ if (rom_info_start != NULL)
+ {
+ /* End of the .bin file that became the .rom file. */
+ uint8_t *bin_eof = rom_info_start;
+
+ //Pass boot parameters.
+ //As a consequence, the rom info that was written to Cesar memory
+ //will be overwritten. But that is not a problem because the rom
+ //info is not used on Cesar side.
+ plcdrv_pass_boot_params(bin_eof);
+ }
+
+ //LEON code downloaded let's start network device
+ priv->firmware_written = 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Write on the character device,
+ * to download LEON binary.
+ *
+ * \param filp file structure.
+ * \param buf user data pointer.
+ * \param pos position.
+ * \return error code.
+ */
+int plcdrv_char_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ uint8_t *ptr;
+
+ //Offset calculation
+ ptr = (uint8_t*)VIRT_PLCCODE_BASE + *f_pos;
+
+ //Download binary into Leon base address
+ if(copy_from_user(ptr, buf, count))
+ {
+ return -EFAULT;
+ }
+ *f_pos += count;
+
+ //Write at least one packet, prepare on start network device
+ write_called = 1;
+ return count;
+}
+
+/**
+ * Read on the character device,
+ * normally not used.
+ *
+ * \param filp file structure.
+ * \param buf user data pointer.
+ * \param pos position.
+ * \return error code.
+ */
+int plcdrv_char_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ uint32_t *ptr;
+
+ //Offset calculation
+ ptr = (uint32_t*)VIRT_PLCCODE_BASE + *f_pos;
+
+ //Load binary from Leon base address
+ if(copy_to_user(buf, ptr, count))
+ {
+ return -EFAULT;
+ }
+ *f_pos += count;
+ return count;
+}
+
+/**
+ * Poll the character device.
+ *
+ * \param filp file structure
+ * \param wait poll table structure
+ * \return error code
+ */
+unsigned int plcdrv_char_poll(struct file *filp, poll_table * wait)
+{
+ struct net_device *dev = NULL;
+ struct net_priv *priv = NULL;
+
+ if (NULL == filp)
+ return POLLERR;
+
+ dev = filp->private_data;
+ if (NULL == dev)
+ return POLLERR;
+
+ priv = (struct net_priv *) dev->priv;
+ if (NULL == priv)
+ return POLLERR;
+
+ poll_wait (filp, &priv->plc_select.wq, wait);
+
+ if (1 == atomic_read (&priv->plc_select.plc_error))
+ return POLLPRI;
+
+ return 0;
+}
+
+int trace_cfops_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ static void *debug_dump_buffer = NULL;
+ static uint debug_dump_buffer_remaining_length = 0;
+ uint copied_data;
+ int error;
+
+ //Check used parameter.
+ BUG_ON(f_pos == NULL);
+ //Sanity check.
+ BUG_ON(debug_dump_opened == false);
+
+ //Allocate debug dump buffer if needed.
+ if(!debug_dump_buffer)
+ {
+ //Allocate.
+ error = alloc_debug_dump_buffer(&debug_dump_buffer,
+ debug_dump_buffer_length);
+ if(error != 0)
+ return error;
+ }
+
+ //If we have nothing to copy to user.
+ if(debug_dump_buffer_remaining_length == 0)
+ {
+ //We need to handle the case where the buffer has been received while
+ //this code is not executed anymore (ctrl+c for example). There can be
+ //three cases:
+ // - we need to send a new buffer to Cesar because we do not have one
+ // to deal with,
+ // - the buffer has already been sent (debug_dump_waiting_for_buffer
+ // is set to true) but still not received
+ // (debug_dump_buffer_length_received set to -1): we need to wait.
+ // - the buffer has been received (debug_dump_buffer_length_received
+ // is not -1) but not processed by this code
+ // (debug_dump_waiting_for_buffer is set to false).
+
+ //Give debug dump buffer to CESAR if this not already done.
+ if(debug_dump_waiting_for_buffer == false)
+ {
+ //Debug dump buffer has been given to Cesar.
+ debug_dump_buffer_length_received = -1;
+ debug_dump_waiting_for_buffer = true;
+ error = processing_debug_dump_buffer_send(debug_dump_buffer);
+ if (error != 0)
+ {
+ free_debug_dump_buffer(debug_dump_buffer,
+ debug_dump_buffer_length);
+ debug_dump_buffer = NULL;
+ debug_dump_waiting_for_buffer = false;
+ return error;
+ }
+ }
+
+ //Go to sleep until debug dump buffer has returned.
+ error = wait_event_interruptible
+ (debug_dump_wait_queue,
+ debug_dump_buffer_length_received != -1);
+ if (error != 0)
+ return error;
+
+ //Debug dump buffer received from Cesar.
+ debug_dump_waiting_for_buffer = false;
+ debug_dump_buffer_remaining_length = debug_dump_buffer_length_received;
+ }
+
+ //If this is not the last debug dump buffer.
+ if(debug_dump_buffer_remaining_length)
+ {
+ BUG_ON(debug_dump_buffer_remaining_length
+ > debug_dump_buffer_length_received);
+ //How much can we copy?
+ copied_data = min(count, debug_dump_buffer_remaining_length);
+ //Copy debug dump buffer to buffer for reading.
+ if(copy_to_user
+ (buf,
+ debug_dump_buffer
+ + (debug_dump_buffer_length_received
+ - debug_dump_buffer_remaining_length),
+ copied_data))
+ {
+ return -EFAULT;
+ }
+ debug_dump_buffer_remaining_length -= copied_data;
+
+ }
+ else
+ {
+ //Finish, let's clean.
+ free_debug_dump_buffer(debug_dump_buffer,
+ debug_dump_buffer_length);
+ debug_dump_buffer = NULL;
+ debug_dump_buffer_remaining_length = 0;
+
+ copied_data = 0;
+ }
+
+ //Update what have been done.
+ *f_pos += copied_data;
+ return copied_data;
+}
+
+int trace_cfops_open(struct inode *inp, struct file *filp)
+{
+ //Open only one time.
+ if(!debug_dump_opened)
+ {
+ debug_dump_opened = true;
+ return 0;
+ }
+ else
+ return -EBUSY;
+}
+
+int trace_cfops_release(struct inode *inp, struct file *filp)
+{
+ BUG_ON(debug_dump_opened == false);
+
+ //Device now closed.
+ debug_dump_opened = false;
+ return 0;
+}
+
+/**
+ * Find with data packet address the sk_buff structure address,
+ * this address was stored in a Linux list.
+ *
+ * \param data_addr the packet data address.
+ * \return sk_buff address.
+ */
+static struct sk_buff* get_skb_addr(uint32_t data_addr)
+{
+ struct skb_addr_list *entry;
+ struct sk_buff *skb = NULL;
+ struct net_priv *priv = plcdrv_device->priv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ //Check in the list to find the sk_buff corresponding to our data_addr
+ //No need to use the _safe version as we stop as soon we have deleted the
+ //element.
+ list_for_each_entry(entry, &priv->list_head_skbs, list)
+ {
+ if(((uint32_t)entry->pkt_addr) == data_addr)
+ {
+ //Get the content.
+ skb = entry->skb;
+ //We should never have a match without any sk_buff.
+ BUG_ON(skb == NULL);
+ //Remove the element first.
+ list_del(&entry->list);
+ //Delete the entry.
+ kfree(entry);
+ //Go out of the loop.
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return skb;
+}// get_skb_addr
+
+/**
+ * Store the sk_buff->data address in a Linux list
+ * this address is associated with sk_buff address.
+ *
+ * \param skb the sk_buff address.
+ * \return error code.
+ */
+static uint32_t put_skb_addr(struct sk_buff *skb)
+{
+ struct skb_addr_list *entry;
+ struct net_priv *priv = plcdrv_device->priv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ //Create a new entry for the list of sk_buffs in used
+ entry = (struct skb_addr_list*)kmalloc(sizeof(struct skb_addr_list), GFP_ATOMIC);
+ if(entry == NULL)
+ {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return -ENOMEM;
+ }
+
+ entry->skb = skb;
+ entry->pkt_addr = (uint32_t*)skb->data;
+
+ //Add the new entry in the list
+ list_add_tail(&entry->list, &priv->list_head_skbs);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return 0;
+}// put_skb_addr
+
+uint32_t prepare_buffer_to_hw(uint32_t addr, unsigned int len,
+ enum data_direction data_dir)
+{
+ return dma_map_single(NULL, (void *) addr, len, data_dir);
+}// prepare_buffer_to_hw
+
+uint32_t prepare_buffer_from_hw(uint32_t addr, unsigned int len,
+ enum data_direction data_dir)
+{
+ dma_unmap_single(NULL, addr, len, data_dir);
+
+ //Return the corresponding virtual addr
+ return (uint32_t)(dma_to_virt(NULL, addr));
+}// prepare_buffer_from_hw
+
+/**
+ * Allocate 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;
+ int result;
+
+ //Allocate an sk_buff
+ skb = alloc_skb(PKT_BUF_SZ, GFP_ATOMIC | GFP_DMA);
+ if(!skb)
+ {
+ printk(KERN_ERR DRV_NAME": Error allocating RX buffer for %s\n",plcdrv_device->name);
+ return -ENOMEM;
+ }
+
+ TRACE("AddBuffer: virt@skb=%x ; virt@skb->data=%x\n",(uint32_t)skb, (uint32_t)skb->data);
+
+ //Store the sk_buff in sk_buff list in used
+ if((result = put_skb_addr(skb)))
+ {
+ kfree_skb(skb);
+ return result;
+ }
+
+ //Send this allocated pointer to lower layer
+ if((result = processing_buffer_add((void*)skb->data, type)))
+ {
+ kfree_skb(skb);
+ }
+
+ return result;
+}// alloc_buffer
+
+int alloc_debug_dump_buffer(void **debug_dump_buffer,
+ int debug_dump_buffer_length)
+{
+ //Check parameters.
+ BUG_ON(debug_dump_buffer == NULL || *debug_dump_buffer != NULL);
+
+ //Allocate debug dump buffer.
+ *debug_dump_buffer = kmalloc(debug_dump_buffer_length, GFP_ATOMIC | GFP_DMA);
+
+ if(!*debug_dump_buffer)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * 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 = NULL;
+ struct sk_buff *skb;
+
+ //Check pointers
+ priv = (struct net_priv *)plcdrv_device->priv;
+ if(priv == NULL)
+ return -1;
+
+ if(packet)
+ {
+ //Find the sk_buff associated to this packet
+ skb = get_skb_addr((uint32_t)packet);
+ TRACE("FreeBuffer: virt@skb=%x ; virt@skb->data=%x ; @packet=%x\n",(uint32_t)skb, (uint32_t)skb->data,(uint32_t)packet);
+ if(!skb)
+ {
+ printk(KERN_ERR DRV_NAME": %s: error getting sb_buff from received pointer\n", plcdrv_device->name);
+ return -1;
+ }
+
+ //Check the free reason for stats
+ switch(reason)
+ {
+ case RX_DROP: priv->stats.rx_dropped++;
+ break;
+ case TX_DROP: priv->stats.tx_dropped++;
+ priv->plc_stats.tx_pool--;
+ if(netif_queue_stopped(plcdrv_device))
+ netif_wake_queue(plcdrv_device);
+ break;
+ case TX_COMPLETE: priv->stats.tx_packets++;
+ priv->stats.tx_bytes += skb->len;
+ priv->plc_stats.tx_pool--;
+ if(netif_queue_stopped(plcdrv_device))
+ netif_wake_queue(plcdrv_device);
+ break;
+ default:
+ break;
+ }
+
+ //Free sk_buff
+ kfree_skb(skb);
+
+ return 0;
+ }
+ else
+ {
+ printk(KERN_ERR DRV_NAME": %s: error freeing a NULL buffer\n", plcdrv_device->name);
+ return -1;
+ }
+}// free_buffer
+
+void free_debug_dump_buffer(void *debug_dump_buffer,
+ int debug_dump_buffer_length)
+{
+ //Check parameters.
+ BUG_ON(debug_dump_buffer == NULL || debug_dump_buffer_length == 0);
+
+ //Free.
+ kfree(debug_dump_buffer);
+}
+
+/**
+ * Receive a packet that need to transit through NETLINK.
+ *
+ * \param skb frame structure.
+ * \param dev device structure config
+ * \param sock netlink socket where to transmit
+ * \param pid netlink message pid
+ */
+void plcdrv_netlink_rx(struct sk_buff *skb, struct net_device *dev, struct sock *sock, uint32_t pid)
+{
+ struct net_priv *priv;
+ struct sk_buff *nlskb;
+ struct nlmsghdr *nlh;
+
+ //Check pointers
+ if(skb == NULL)
+ return;
+ if(dev == NULL)
+ return;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return;
+
+ //Allocate a new sk_buff to add netlink header
+ nlskb = alloc_skb(NLMSG_LENGTH(skb->len), GFP_ATOMIC);
+ if(!nlskb)
+ {
+ printk(KERN_ERR DRV_NAME": %s: Error allocating a netlink sk_buff\n", dev->name);
+ return;
+ }
+
+ //Fill netlink header
+ nlh = NLMSG_PUT(nlskb, 0, 0, NLMSG_DONE, NLMSG_LENGTH(skb->len) - sizeof(*nlh));
+ NETLINK_CB(nlskb).pid = 0; //from kernel
+ NETLINK_CB(nlskb).dst_group = 0; //unicast
+
+ //Fill this new sk_buff with the old one after netlink header
+ memcpy(NLMSG_DATA(nlh), skb->data, skb->len);
+
+ //Suppress old sk_buff
+ kfree_skb(skb);
+
+ //Send netlink to plcd
+ netlink_unicast(sock, nlskb, pid, MSG_DONTWAIT);
+
+ return;
+
+nlmsg_failure:
+ kfree_skb(skb);
+ kfree_skb(nlskb);
+}// plcdrv_netlink_rx
+
+/**
+ * Receive a packet.
+ *
+ * \param packet packet pointer.
+ * \param length packet length.
+ * \return error code.
+ */
+int plcdrv_rx(void *packet, int length, enum pkt_dest dst)
+{
+ struct net_priv *priv = NULL;
+ struct sk_buff *skb;
+ int result;
+
+ priv = (struct net_priv *)plcdrv_device->priv;
+ if(priv == NULL)
+ return -1;
+
+ //Check packet length size
+ if((length <= 0) || (length > PKT_BUF_SZ))
+ return -1;
+
+ if(packet)
+ {
+
+ //Find the sk_buff address
+ skb = get_skb_addr((uint32_t)packet);
+
+ //Pass data to the Linux internal receive level
+ skb->dev = plcdrv_device;
+ skb_put(skb, length);
+ TRACE("\nRX: virt@skb=%x ; virt@skb->data=%x ; @packet=%x\n",(uint32_t)skb, (uint32_t)skb->data,(uint32_t)packet);
+ PRINTPKT("RX",skb, length);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ priv->stats.rx_packets++;
+ priv->stats.rx_bytes += length;
+ if(dst == NETLINK_DRV)
+ {
+ plcdrv_netlink_rx(skb, plcdrv_device, priv->nl_drv_sock, priv->nl_drv_pid);
+ }
+ else if(dst == NETLINK_MME)
+ {
+ plcdrv_netlink_rx(skb, plcdrv_device, priv->nl_mme_sock, priv->nl_mme_pid);
+ }
+ else
+ {
+ skb->protocol = eth_type_trans(skb, plcdrv_device);
+
+ seq_check_rx(&priv->seq_check_ctx, skb);
+
+ netif_rx(skb);
+ }
+ result = 0;
+ }
+ else
+ {
+ printk(KERN_ERR DRV_NAME": %s: Error Receiving a NULL buffer\n", plcdrv_device->name);
+ result = -1;
+ }
+ return result;
+}// plcdrv_rx
+
+static void plcdrv_post_tx (struct sk_buff *skb, struct net_device *dev, int status)
+{
+ struct net_priv *priv = NULL;
+ priv = (struct net_priv *)dev->priv;
+
+ //Tx queue is nearly full we must stop it
+ if(status == NEARLY_FULL ||
+ priv->plc_stats.tx_pool == MBX_TX_POOL - 1)
+ {
+ netif_stop_queue(dev);
+ priv->plc_stats.tx_pool++; // update plc stats
+ TRACE("TX queue nearly Full\n");
+ }
+ //Tx queue is full drop the frame
+ else if(status == FULL)
+ {
+ netif_stop_queue(dev);
+ priv->stats.tx_errors++;
+ priv->stats.tx_fifo_errors++;
+ kfree_skb(skb);
+ printk(KERN_WARNING DRV_NAME ": %s: TX queue is Full\n", dev->name);
+ }
+ // Transmit success
+ else
+ {
+ priv->plc_stats.tx_pool++; // update plc stats
+ }
+
+ //Handle transmit
+ dev->trans_start = jiffies;
+
+ return;
+}
+
+/**
+ * Transmit frame procedure.
+ *
+ * \param skb frame structure.
+ * \param dev device structure.
+ * \return error code.
+ */
+int plcdrv_data_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_priv *priv = NULL;
+ int status;
+
+ //Check pointers
+ if(skb == NULL)
+ return -1;
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ TRACE("\nTX: virt@skb=%x ; virt@skb->data=%x ; skb->len=%d\n",(uint32_t)skb, (uint32_t)skb->data, skb->len);
+ PRINTPKT("TX",skb, skb->len);
+
+ seq_check_tx(&priv->seq_check_ctx, skb);
+
+ //Store the sk_buff in sk_buff list in used
+ if((status = put_skb_addr(skb)))
+ {
+ return status;
+ }
+
+ //Send buffer to lower layers
+ status = processing_send((void *)skb->data, skb->len, DATA);
+
+ /* check result and update stats */
+ plcdrv_post_tx (skb, dev, status);
+
+ return 0;
+}// plcdrv_data_tx
+
+/**
+ * Transmit a frame received from NETLINK.
+ *
+ * \param skb frame structure.
+ * \param dev net device structure
+ * \param sock netlink socket structure
+ */
+int plcdrv_netlink_tx(struct sk_buff *nlskb, struct net_device *dev, struct sock *sock)
+{
+ struct sk_buff *skb;
+ struct net_priv *priv;
+ buffer_type_t type;
+ int status;
+
+ //Check pointers
+ if(nlskb == NULL)
+ return -1;
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ mutex_lock(&plcdrv_nl_mutex);
+
+ //Prepare a sk_buff without netlink header
+ skb_pull(nlskb, NLMSG_HDRLEN);
+
+ //Allocate a new sk_buff because nlskb will be suppress at this end of this function
+ skb = alloc_skb(nlskb->len, GFP_ATOMIC | GFP_DMA);
+ if(!skb)
+ {
+ printk(KERN_ERR DRV_NAME": %s: Error allocating a netlink sk_buff\n", dev->name);
+ return -1;
+ }
+
+ //Fill this new sk_buff with the old one
+ memcpy(skb->data, nlskb->data, nlskb->len);
+
+ //Prepare this new sk_buff
+ skb_put(skb, nlskb->len);
+
+ TRACE("\nTX: virt@skb=%x ; virt@skb->data=%x ; skb->len=%d\n",(uint32_t)skb, (uint32_t)skb->data, skb->len);
+ PRINTPKT("TX",skb, skb->len);
+
+ //Store the sk_buff in sk_buff list in used
+ if((status = put_skb_addr(skb)))
+ {
+ return status;
+ }
+
+ if((get_eth_mme_type(skb->data) == HPAV_MME_P_FCALL) ||
+ (get_eth_mme_type(skb->data) == HPAV_MME_P_SNIFFER))
+ {
+ type = INTERFACE;
+ }
+ else
+ {
+ type = MME;
+ }
+ //send buffer to lower layer
+ status = processing_send((void *)skb->data, skb->len, type);
+
+ /* check result and update stats */
+ plcdrv_post_tx (skb, dev, status);
+
+ mutex_unlock(&plcdrv_nl_mutex);
+
+ return 0;
+}// plcdrv_netlink_tx
+
+/**
+ * Transmit a frame received from DRV NETLINK.
+ *
+ * \param skb frame structure.
+ */
+void plcdrv_netlink_drv_tx (struct sk_buff *nlskb)
+{
+ struct net_priv *priv = (struct net_priv *)plcdrv_device->priv;
+ struct nlmsghdr *nlh;
+ if(NULL == nlskb)
+ return;
+ nlh = nlmsg_hdr(nlskb);
+ plcdrv_netlink_tx(nlskb, plcdrv_device, priv->nl_drv_sock);
+}
+
+/**
+ * Transmit a frame received from MME NETLINK.
+ *
+ * \param skb frame structure.
+ */
+void plcdrv_netlink_mme_tx (struct sk_buff *nlskb)
+{
+ struct net_priv *priv = (struct net_priv *)plcdrv_device->priv;
+ struct nlmsghdr *nlh;
+ if(NULL == nlskb)
+ return;
+ nlh = nlmsg_hdr(nlskb);
+ plcdrv_netlink_tx(nlskb, plcdrv_device, priv->nl_mme_sock);
+}
+
+/**
+ * Interrupt Handler Watchdog procedure.
+ *
+ * \param irq interrupt number
+ * \param dev_id device structure
+ * \return error code
+ */
+irqreturn_t
+plcdrv_it_wd (int irq, void * dev_id)
+{
+ struct net_device *dev = NULL;
+ struct net_priv *priv = NULL;
+
+ if (NULL == dev_id)
+ return IRQ_NONE;
+
+ dev = (struct net_device *) dev_id;
+ priv = netdev_priv(dev);
+ if (NULL == priv)
+ return IRQ_NONE;
+
+ /* Is some process waiting on a select() ? */
+ if (!waitqueue_active (&priv->plc_select.wq))
+ {
+ /* Reset */
+ /* Can't use one of the kernel reboot functions (like kernel_restart())
+ * because of the EXPORT_SYMBOL_GPL. */
+ RB_RST_GLOBAL_VA = 1;
+ }
+ else
+ {
+ /* Wake up the process waiting on the select(), and let it handle the
+ * situation. */
+ atomic_set (&priv->plc_select.plc_error, 1);
+ wake_up (&priv->plc_select.wq);
+ L2Awd_it_disable (priv->halctx);
+ }
+
+ return IRQ_HANDLED;
+} // plcdrv_it_wd
+
+/**
+ * Interrupt Handler Receive procedure.
+ *
+ * \param irq interrupt number.
+ * \param dev_id device structure.
+ * \return error code.
+ */
+irqreturn_t plcdrv_it_rx(int irq, void * dev_id)
+{
+ struct net_device *dev = NULL;
+ struct net_priv *priv = NULL;
+
+ //Check pointer
+ dev = (struct net_device*)dev_id;
+ if(dev == NULL)
+ return IRQ_NONE;
+
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return IRQ_NONE;
+
+ //Call mailbox receive for interrupt part
+ mailbox_receive_isr();
+
+ //Call the tasklet for real mailbox reception part
+ tasklet_schedule(&priv->tasklet_it_rx);
+
+ return IRQ_HANDLED;
+}// plcdrv_it_rx
+
+/**
+ * Bottom Half Receive procedure.
+ *
+ * \param dev_id device structure.
+ */
+void plcdrv_bh_rx(unsigned long dev_id)
+{
+ struct net_device *dev = NULL;
+ struct net_priv *priv = NULL;
+ unsigned int budget;
+
+ //Check pointer
+ dev = (struct net_device*)dev_id;
+ if(dev == NULL)
+ return;
+
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return;
+
+ budget = PLCDRV_RX_BUDGET;
+
+ //Call mailbox receive
+ mailbox_receive(&budget);
+
+ //Re-call us if budget is exhausted
+ if(!budget)
+ tasklet_schedule(&priv->tasklet_it_rx);
+
+}// plcdrv_bh_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;
+
+ //Check pointer
+ if(dev_id == NULL)
+ return IRQ_NONE;
+
+ dev = (struct net_device*)dev_id;
+ TRACE("Transmit Done IT\n");
+
+ //A packet was just freeing by the hardware,
+ //we can restart the tx queue
+ if(netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+
+ //call lowest layer that a tx_done is arrived
+ mailbox_txdone();
+ TRACE("Transmit Done IT end\n");
+
+ 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;
+
+ //Give statistics
+ 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;
+
+ TRACE("change_mtu\n");
+
+ //Check arguments
+ if(new_mtu < 64 || new_mtu > 1508)
+ return -EINVAL;
+ else
+ {
+ //Change the MTU
+ dev->mtu = new_mtu;
+ TRACE("change_mtu end\n");
+ 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("set_mac_address\n");
+
+ //Store the new address for Linux
+ memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
+
+ TRACE("set_mac_address end\n");
+
+ 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)
+{
+ struct net_priv *priv = NULL;
+ struct plcdrv_setpid user_data = {0};
+
+ //Check pointers
+ if(ifr == NULL)
+ return -1;
+ if(dev == NULL)
+ return -1;
+
+ TRACE("ioctl\n");
+
+ //Find the command
+ switch(cmd)
+ {
+ case PLCDRV_IOCTL_SETPID:
+
+ /* Check validity of driver private data */
+ if (NULL == (priv = (struct net_priv *) dev->priv))
+ return -EFAULT;
+
+ /* Get user data */
+ if (copy_from_user (&user_data, ifr->ifr_data, sizeof (user_data)))
+ return -EFAULT;
+
+ /* During initialization, plcd & managerd register their pid
+ * for reception on drv & mme netlink */
+ if (NETLINK_PLC_DRV == user_data.nl)
+ {
+ priv->nl_drv_pid = user_data.pid;
+ printk (KERN_INFO "%s: plcd registered with pid %d\n", __FUNCTION__,
+ priv->nl_drv_pid);
+ }
+ if (NETLINK_PLC_MME == user_data.nl)
+ {
+ priv->nl_mme_pid = user_data.pid;
+ printk (KERN_INFO "%s: managerd registered with pid %d\n", __FUNCTION__,
+ priv->nl_mme_pid);
+ }
+
+ /* In case of another netlink, do nothing */
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ TRACE("ioctl end\n");
+
+ 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;
+ struct init_info info;
+ int i;
+ int result;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ //Wait Downloading Leon binary into RAM to really open the driver
+ if(!priv->firmware_written)
+ {
+ printk(KERN_ERR DRV_NAME": %s You need to download SPiDCOM PLC binary before\n",dev->name);
+ return -EPERM;
+ }
+
+ //Prepare mutex
+ spin_lock_init(&priv->lock);
+
+ //Prepare sk_buff in used list
+ INIT_LIST_HEAD(&priv->list_head_skbs);
+
+ //Configure AFE with the default configuration
+ if (init_afe())
+ return -EFAULT;
+
+ //Allocate rings for each mailbox
+ if((priv->virt_ring_base_addr =
+ (uint32_t)dma_alloc_coherent(NULL,
+ A2L_RING_SIZE+L2A_RING_SIZE,
+ &priv->phys_ring_base_addr,
+ GFP_ATOMIC|GFP_DMA)
+ ) == 0)
+ {
+ printk(KERN_ERR DRV_NAME": Error allocating mailboxes rings for %s\n", dev->name);
+ return -ENOMEM;
+ }
+ TRACE("OPEN: virt@rings=%x ; phys@rings=%x\n",priv->virt_ring_base_addr, priv->phys_ring_base_addr);
+
+ //Flush rings for each mailbox
+ memset((void*)priv->virt_ring_base_addr, 0, A2L_RING_SIZE+L2A_RING_SIZE);
+
+ //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);
+ result = -ENODEV;
+ goto err_open;
+ }
+
+ //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);
+ result = -ENODEV;
+ goto err_rq_ack;
+ }
+
+ //Request Leon Watchdog IRQ
+ if(request_irq(priv->num_mbx_it_wd, plcdrv_it_wd, 0, dev->name, dev) != 0)
+ {
+ printk(KERN_ERR DRV_NAME ": %s - interrupt %d request fail\n", dev->name, dev->irq);
+ result = -ENODEV;
+ goto err_rq_wd;
+ }
+
+ //Unreset Leon Processor
+ if(plcdrv_reset_leon(0))
+ {
+ result = -1;
+ goto err_rq_wd;
+ }
+
+ //Start lower Layers
+ info.ring_base_addr = priv->virt_ring_base_addr;
+ info.phys_ring_base_addr = priv->phys_ring_base_addr;
+ info.mbx_reg_base_addr = dev->base_addr;
+ info.debug_mode = debug;
+ info.launch_leon = &plcdrv_launch_leon;
+ memcpy(info.mac_addr, dev->dev_addr, sizeof(info.mac_addr));
+ if(processing_init(&info, dev))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: Error initializing hardware (firmware)\n", dev->name);
+ result = -1;
+ goto err_rq_wd;
+ }
+
+ //Allocate RX buffer pool to give to CESAR
+ for(i=0 ; i<nb_rx_data_buffers ; i++)
+ {
+ if(alloc_buffer(DATA))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: Error creating DATA buffer pool\n", dev->name);
+ result = -1;
+ goto err_rq_wd;
+ }
+ // update plc stats
+ priv->plc_stats.rx_pool++;
+ }
+ for(i=0 ; i<nb_rx_mme_buffers ; i++)
+ {
+ if(alloc_buffer(MME))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: Error creating MME buffer pool\n", dev->name);
+ result = -1;
+ goto err_rq_wd;
+ }
+ }
+ for(i=0 ; i<nb_rx_interface_buffers ; i++)
+ {
+ if(alloc_buffer(INTERFACE))
+ {
+ printk(KERN_ERR DRV_NAME ": %s: Error creating INTERFACE buffer pool\n", dev->name);
+ result = -1;
+ goto err_rq_wd;
+ }
+ // update plc stats
+ priv->plc_stats.rx_pool++;
+ }
+
+ //Enable tasklet for reception
+ tasklet_enable(&priv->tasklet_it_rx);
+
+ //Initialize netlink functions
+ priv->nl_drv_sock = netlink_kernel_create(&init_net, NETLINK_PLC_DRV, 0, plcdrv_netlink_drv_tx, NULL, THIS_MODULE);
+ priv->nl_mme_sock = netlink_kernel_create(&init_net, NETLINK_PLC_MME, 0, plcdrv_netlink_mme_tx, NULL, THIS_MODULE);
+
+ //Prepare Linux as link up
+ netif_carrier_on(dev);
+ netif_start_queue(dev);
+
+ return 0;
+
+err_rq_wd:
+ free_irq(priv->num_mbx_it_ack, dev);
+err_rq_ack:
+ free_irq(priv->num_mbx_it, dev);
+err_open:
+ dma_free_coherent(NULL,
+ A2L_RING_SIZE+L2A_RING_SIZE,
+ (void*)priv->virt_ring_base_addr,
+ priv->phys_ring_base_addr);
+ return result;
+}// plcdrv_open
+
+/**
+ * Uninitialize the device.
+ *
+ * \param dev device structure.
+ * \return error code.
+ */
+int plcdrv_stop(struct net_device *dev)
+{
+ struct net_priv *priv = NULL;
+ struct skb_addr_list *entry, *n;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ TRACE("stop\n");
+
+ //Disable transmitter
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+
+ //Stop netlink interface
+ netlink_kernel_release(priv->nl_drv_sock);
+ netlink_kernel_release(priv->nl_mme_sock);
+
+ //Stop tasklet for reception
+ tasklet_disable(&priv->tasklet_it_rx);
+
+ //Stop lower layers
+ processing_uninit();
+
+ //Reset LEON and DSP
+ plcdrv_reset_leon(1);
+ plcdrv_reset_dsp(1);
+ plcdrv_reset_dsp(0);
+
+ //Disconnect from IRQ
+ free_irq(priv->num_mbx_it, dev);
+ free_irq(priv->num_mbx_it_ack, dev);
+ free_irq(priv->num_mbx_it_wd, dev);
+
+ //Free mailboxes ring
+ dma_free_coherent(NULL,
+ A2L_RING_SIZE+L2A_RING_SIZE,
+ (void*)priv->virt_ring_base_addr,
+ priv->phys_ring_base_addr);
+
+ //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)
+ //
+ //Currently, we only suppress all sk_buff in used
+ list_for_each_entry_safe (entry, n, &priv->list_head_skbs, list)
+ {
+ //Remove the element first.
+ list_del(&entry->list);
+ //Delete the content of the element.
+ kfree_skb(entry->skb);
+ //Delete the element itself.
+ kfree(entry);
+ }
+
+ TRACE("stop end\n");
+
+ 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;
+ uint32_t *leon_start_addr_ptr;
+
+ //Check pointers
+ if(dev == NULL)
+ return -1;
+ priv = (struct net_priv *)dev->priv;
+ if(priv == NULL)
+ return -1;
+
+ //Set IP base address
+ dev->base_addr = (unsigned int)ioremap(MBX_BASE_ADDR, NB_MBX_REGISTERS);
+ TRACE("INIT: virt@mbx_reg=%x ; phys@mbx_reg=%x\n",(uint32_t)dev->base_addr,MBX_BASE_ADDR);
+
+ //Set Interrupts numbers
+ dev->irq = INT_MBX; //do not use dev->irq because there is more than one IT given by the hardware
+ priv->num_mbx_it = INT_MBX;
+ priv->num_mbx_it_ack = INT_MBX_ACK;
+ priv->num_mbx_it_wd = INT_MBX_WD;
+
+ //Set Leon code base address (translation)
+ leon_start_addr_ptr = (uint32_t*)ioremap(RB_LEON_ADD_START, 1);
+ //If a custom value was passed as a module parameter, use it.
+ //Otherwise, use the default one.
+ *leon_start_addr_ptr = (leon_start_addr != INVALID_LEON_START_ADDR) ?
+ leon_start_addr : spc300_plc_mem_start;
+ iounmap(leon_start_addr_ptr);
+
+ //Force waiting download Leon binary
+ priv->firmware_written = 0;
+
+ //Initialise device functions
+ ether_setup(dev);
+ dev->uninit = plcdrv_uninit;
+ 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_data_tx;
+ dev->get_stats = plcdrv_stats;
+/* dev->tx_timeout = plcdrv_tx_timeout; */
+/* dev->watchdog_timeo = TX_TIMEOUT; */
+ dev->change_mtu = plcdrv_change_mtu;
+
+ //Declare tasklet for reception
+ tasklet_init(&priv->tasklet_it_rx, &plcdrv_bh_rx, (unsigned long)dev);
+ tasklet_disable(&priv->tasklet_it_rx);
+
+ //Setup MAC address for Linux (stored in NVRAM)
+ dev->dev_addr[0] = spc300_nvram.plc_address[0];
+ dev->dev_addr[1] = spc300_nvram.plc_address[1];
+ dev->dev_addr[2] = spc300_nvram.plc_address[2];
+ dev->dev_addr[3] = spc300_nvram.plc_address[3];
+ dev->dev_addr[4] = spc300_nvram.plc_address[4];
+ dev->dev_addr[5] = spc300_nvram.plc_address[5];
+
+ // Initialize structure for poll/select management
+ atomic_set (&priv->plc_select.plc_error, 0);
+ init_waitqueue_head (&priv->plc_select.wq);
+
+ seq_check_init(&priv->seq_check_ctx, "plc_drv");
+
+ return 0;
+}// plcdrv_init
+
+/**
+ * Uninitialise the network device.
+ *
+ * \param dev device structure.
+ */
+void plcdrv_uninit(struct net_device *dev)
+{
+ struct net_priv *priv;
+ BUG_ON(!dev);
+ priv = (struct net_priv *)dev->priv;
+ BUG_ON(!priv);
+
+ //Kill tasklet for reception
+ tasklet_kill(&priv->tasklet_it_rx);
+
+ //Unmap IP address
+ BUG_ON(!dev->base_addr);
+ iounmap((void*)dev->base_addr);
+}// plcdrv_uninit
+
+/** Character device functions */
+static struct file_operations plcdrv_char_fops = {
+ .owner = THIS_MODULE,
+ .open = plcdrv_char_open,
+ .read = plcdrv_char_read,
+ .write = plcdrv_char_write,
+ .release = plcdrv_char_close,
+ .poll = plcdrv_char_poll,
+};
+
+static struct file_operations trace_cfops = {
+ .owner = THIS_MODULE,
+ .read = trace_cfops_read,
+ .open = trace_cfops_open,
+ .release = trace_cfops_release,
+};
+
+/**
+ * Initialise the module.
+ *
+ * \return error code.
+ */
+int __init plcdrv_module_init(void)
+{
+ int result;
+ struct net_device *dev = NULL;
+ struct proc_dir_entry *entry;
+
+ printk("%s", version);
+
+ //Register a character device to manage Cesar Binary download
+ //Allocate character device Major number
+ result = alloc_chrdev_region(&number, 0, 2, "plcdrv");
+ if(result < 0)
+ {
+ printk(KERN_ERR "plcdrv: can't get major %d\n",MAJOR(number));
+ return result;
+ }
+ //Register character device
+ cdev_init(&plcdrv_char_dev,&plcdrv_char_fops);
+ plcdrv_char_dev.owner = THIS_MODULE;
+ plcdrv_char_dev.ops = &plcdrv_char_fops;
+ result = cdev_add(&plcdrv_char_dev, number, 1);
+ if(result <0)
+ {
+ printk(KERN_ERR "plcdrv: Error registering\n");
+ unregister_chrdev_region(number, 2);
+ return result;
+ }
+
+ // Register trace character device.
+ cdev_init(&trace_cdev, &trace_cfops);
+ trace_cdev.owner = THIS_MODULE;
+ result = cdev_add(&trace_cdev, MKDEV(MAJOR(number), MINOR(number) + 1), 1);
+ if(result<0)
+ {
+ printk (KERN_ERR "plcdrv: error registering trace device\n");
+ cdev_del(&plcdrv_char_dev);
+ unregister_chrdev_region(number, 2);
+ }
+
+ //Register the network device
+ //Allocate device memory
+ dev = alloc_netdev(sizeof(struct net_priv), "plc%d", ether_setup);
+ if((dev == NULL) || (dev->priv == NULL))
+ {
+ cdev_del(&plcdrv_char_dev);
+ cdev_del(&trace_cdev);
+ unregister_chrdev_region(number, 2);
+ return -ENOMEM;
+ }
+ //Initialize private structure
+ memset(dev->priv, 0, sizeof(struct net_priv));
+
+ //Proceed the init driver
+ dev->init = plcdrv_init;
+
+ //Register network device
+ result = register_netdev(dev);
+ if(result < 0)
+ {
+ printk(KERN_ERR DRV_NAME": Error %i registering %s\n", result, dev->name);
+ cdev_del(&plcdrv_char_dev);
+ cdev_del(&trace_cdev);
+ unregister_chrdev_region(number, 2);
+ kfree(dev->priv);
+ free_netdev(dev);
+ return result;
+ }
+ else
+ {
+ plcdrv_device = dev;
+ }
+
+ //Create a proc entry for version
+ proc_plc_dir = proc_mkdir("plc", init_net.proc_net);
+ entry = create_proc_entry("version", 0, proc_plc_dir);
+ entry->read_proc = plcdrv_readproc_version;
+ entry->data = (int*)plcdrv_device;
+
+ // Create stats entries
+ entry = create_proc_entry ("stats", 0, proc_plc_dir);
+ entry->read_proc = plcdrv_readproc_plc_stats;
+ entry->data = (int*)plcdrv_device;
+
+ return result;
+}// plcdrv_module_init
+
+/**
+ * Uninitialise the module.
+ */
+void __exit plcdrv_module_exit(void)
+{
+ BUG_ON(!plcdrv_device);
+
+ //Unregister character device
+ cdev_del(&plcdrv_char_dev);
+ cdev_del(&trace_cdev);
+ unregister_chrdev_region(number, 2);
+
+ //Unregister network device
+ unregister_netdev(plcdrv_device);
+ free_netdev(plcdrv_device);
+
+ //Remove proc
+ remove_proc_entry("stats", proc_plc_dir);
+ remove_proc_entry("version", proc_plc_dir);
+ remove_proc_entry("plc", init_net.proc_net);
+}// plcdrv_module_exit
+
+module_init(plcdrv_module_init);
+module_exit(plcdrv_module_exit);
+