Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • atlas-tdaq-felix/felix-versal-tools/flxnet-drivers
1 result
Show changes
Commits on Source (41)
Showing
with 57 additions and 2621 deletions
# Drivers for Host PC and Versal PS communication over PCIe
# Linux drivers for communicating with the FLX-182 card through a virtual network over PCIe
## Motivation
These modules creates a network device on Versal card and host PC. Each FLX-182 card connected to PCIe becomes a peer in the network and is accessible from the host. FLX-182 peers cannot access each other. Host device is assigned a random MAC address on startup. FLX-182 devices are assigned a MAC address generated from IDCODE and VERSION information, so that it stays persistent between reboots.
The goal of the project is to establish communication between Versal PS and Host PC over PCIe so that the image on Versal can be updated. One approach is to leverage the Versal PS running Linux and update the image over the network, since all necessary drivers are already present.
CERN networking policies, however, make this approach difficult from bureaucratic standpoint. A workaround is to tunnel the network traffic to Versal over the PCIe, which is what this project aims to do.
## Instructions
## Drivers
- Instantiate the versal_network_device from <https://gitlab.cern.ch/atlas-tdaq-felix/firmware/-/tree/phase2/FLX-1886_FLX182_virtual_network/sources/ip_cores/BNL182/ip_repo/versal_network_device_1.0?ref_type=heads>
- Connect the AXI bus to the Versal processing system
- Connect the FIFO ports to the the corresponding ports of Wupper
- `character_device` - character device drivers for Versal and Host PC
- `network_device` - network device drivers for Versal and Host PC
- The following modules need to be cross-compiled with petalinux for the Versal card
- Run `petalinux-build -C flxnet` in the petalinux project to build the correct modules:
- flxnet_dev.ko
- flxnet_target.ko
## Questions
- Copy Makefile.host to Makefile on host PC and run `make` to build the correct modules for the host PC:
- flxnet_dev.ko
- flxnet_pcie.ko
Please contact Elena Zhivun (elena.zhivun cern.ch) for questions.
- Add the following device tree entry in the petalinux project (`system-user.dtsi` file in `flx-petalinux/project-spec/meta-user/recipes-bsp/device-tree/files`):
```
&amba {
flxnet_instance: flxnet@20100030000 {
#address-cells = <0x02>;
#size-cells = <0x02>;
reg = <0x201 0x00030000 0x00 0x100>;
compatible = "atlas_tdaq_felix,VERSAL_virtual_network_device_v1.0";
status = "okay";
};
};
```
- The address (the number to the right of '@' and two first numbers in `reg` field) must match the Vivado address editor for the VERSAL_virtual_network IP
- Setup a DHCP server on the host to automatically configure an IP address on the Versal cards
- For debugging messages, increase the message level of the ethernet adapter using the commands:
```
ethtool -s flxnet0 msglvl 4095 # display all debug messages
ethtool -s flxnet0 msglvl 8191 # display all debug messages and packet data
```
- The exact message level may be different for different Linux versions
- For detailed explanation debug message levels see: <https://www.kernel.org/doc/Documentation/networking/netif-msg.txt>
## The modules
- flxnet_dev.ko : network interface representation for the FIFOs, shared module between the Versal and the host, it exports a function that the other modules use for registering the device as a network peer
- flxnet_target.ko : driver for the Versal card, it obtains the IP information from the device tree, then adds the peer to the network
- flxnet_pcie.ko : driver for the host PC, it finds the correct PCIe devices and adds them as peers into the network
## Update
Readme updated on 13-11-2024 by Laura Rientsma <l.rientsma@nikhef.nl>
Original author of the driver is Elena Zhivun, last edited on 05-07-2022
For questions, please contact Frans Schreuder <f.schreuder@nikhef.nl>
Biggest changes:
- Driver is adjusted for and tested with the FLX-182 card instead of the VMK180
- Driver is made compatible with modern Linux designs
- Changed FIFOs into AXI Stream FIFOs, added end of packet signal that fixed the delay of the first few packages
- The driver is written so that multiple flxnet network devices can be created and communicate with one host
- DHCP server on host automatically gives out an IP address to the Versal card instead of having to configure the IP addresses manually
- Added flxnet driver to the build_rpm.sh script from <https://gitlab.cern.ch/atlas-tdaq-felix/felix-tdaq-drivers/-/tree/main?ref_type=heads>, so that the modules on the host PC automatically insert on host server startup
- Added carrier function that gives a signal when the link goes up or down
\ No newline at end of file
ifeq ($(KERNELRELEASE),)
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) KBUILD_EXTRA_SYMBOLS=$(EXTRA_SYMBOLS) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.o.d *.mod
.PHONY: modules modules_install clean
else
# called from kernel build system: just declare what our modules are
obj-m := flx_serial_host.o flx_serial_fake.o flx_serial_target.o flx_serial_dev.o
flx_serial_host-y := pcie_serial.o
flx_serial_fake-y := fake_device.o sysfs_entries.o
flx_serial_target-y := target_serial.o
flx_serial_dev-y := flx_serial_cdev.o file_ops.o
endif
# Example Linux drivers for communicating with vmk180 through a character device over PCIe
These modules create character device on Versal and host PC, which can be used to send data between the devices.
## Instructions
- instantiate vmk180_serial_demo IP in Vivado project, connect it to AXI bus
- if making modifications, ensure that BUF_SIZE in file_ops.h matches prog_full and prog_empty thresholds in vmk180_serial_demo_v1_0_S00_AXI.vhd FIFOs to avoid data loss
- enable PCIe Brigde in Versal design, map the address of the vmk180_serial_demo IP to the PCIe bridge bar (or provide any other way for the host to access vmk180_serial_demo IP registers)
- run `make` on host PC to build all drivers. This will create four .ko files:
- flx_serial_dev.ko - encapsulates FIFO logic logic. This module is shared between the Versal and the host, and exports functions that the other modules use for registering the device
- flx_serial_host.ko - minimal example host PC driver. It finds the correct PCIe device, reserves the bar, then registers /dev/flx_serial0 character device.
- flx_serial_target.ko - example platform driver for the Versal. It obtains the IP information from the device tree, then registers /dev/flx_serial0 character device.
- flx_serial_fake.ko - creates a simulated device /dev/flx_serial0 that can be controlled by writing files in sysfs. The files appear in /sys/kernel/flx_serial_sim once the module is loaded.
- flx_serial_dev.ko and flx_serial_target.ko need to be cross-compiled for Versal, e.g. with petalinux
- Add device tree entry for the vmk180_serial_demo IP. The address (the number to the right of '@' and two first numbers in `reg` field) must match the Vivado address editor for this IP.
```
&amba {
pcie-serial@20100050000 {
#address-cells = <0x02>;
#size-cells = <0x02>;
reg = <0x201 0x00050000 0x00 0x100>;
compatible = "bnl,vmk180-serial-example";
status = "okay";
};
};
```
## Questions
Please contact Elena Zhivun (elena.zhivun cern.ch) for questions.
/*
This is an minimal example of a simulated serial
device to be used for demonstration and debugging
of the vmk180 serial driver.
This driver can be used to create the serial device
on a system where the real vmk180 is not available.
It exposes the register values of the simulated serial
device in sysfs for the user to manipulate.
Parameters are in /sys/kernel/flx_serial_sim
*/
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/kobject.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include "flx_serial_cdev.h"
#include "file_ops.h"
#include "fake_device.h"
#define DRV_MODULE_NAME "flx_serial_sim"
extern int flx_serial_create(struct flx_serial_device* sdev,
void* __iomem base_address, int role, struct module *owner);
extern void flx_serial_destroy(struct flx_serial_device* sdev);
static struct flx_serial_device sdev;
void * sim_iomem;
struct kobject *kobj_ref;
struct kobj_attribute magic_attr = __ATTR(magic, 0660, magic_show, magic_store);
struct kobj_attribute byte_to_recv_attr = __ATTR(byte_to_recv, 0660, recv_show, recv_store);
struct kobj_attribute last_sent_byte_attr = __ATTR(sent_byte, 0660, sent_show, NULL);
struct kobj_attribute can_recv_byte_attr = __ATTR(can_recv_byte, 0660, can_recv_byte_show, can_recv_byte_store);
struct kobj_attribute can_recv_block_attr = __ATTR(can_recv_block, 0660, can_recv_block_show, can_recv_block_store);
struct kobj_attribute can_send_byte_attr = __ATTR(can_send_byte, 0660, can_send_byte_show, can_send_byte_store);
struct kobj_attribute can_send_block_attr = __ATTR(can_send_block, 0660, can_send_block_show, can_send_block_store);
static struct attribute *attrs[] = {
&magic_attr.attr, &byte_to_recv_attr.attr, &last_sent_byte_attr.attr,
&can_recv_byte_attr.attr, &can_recv_block_attr.attr,
&can_send_byte_attr.attr, &can_send_block_attr.attr, NULL,
};
static struct attribute_group attr_group = {
.attrs = attrs,
};
static int __init mod_init(void)
{
int rv;
// pr_info("Initializing simulated memory\n");
sim_iomem = kzalloc(0x1C, GFP_KERNEL);
if (IS_ERR(sim_iomem)) {
pr_err("Failed to allocate memory\n");
rv = -ENOMEM;
return PTR_ERR(sim_iomem);
}
//pr_info("Initializing the register values\n");
register_write(sim_iomem + MAGIC_REG, FLX_DEV_MAGIC);
register_write(sim_iomem + HOST_ADDR_STATUS, 0x0F);
register_write(sim_iomem + HOST_ADDR_RECV, 0x33);
//pr_info("Create the serial device\n");
if (flx_serial_create(&sdev, sim_iomem, ROLE_HOST, THIS_MODULE)) {
pr_err("Failed to create serial device\n");
rv = -1;
goto sdev_create_err;
}
kobj_ref = kobject_create_and_add(DRV_MODULE_NAME, kernel_kobj);
if (IS_ERR(kobj_ref)) {
pr_err("Failed to create kobject %s\n", DRV_MODULE_NAME);
rv = -1;
goto kobj_create_err;
}
//pr_info("Create sysfs entries\n");
if (sysfs_create_group(kobj_ref, &attr_group)) {
rv = -ENOMEM;
pr_err("Failed to create sysfs files\n");
goto err_attr_group;
}
return 0;
err_attr_group:
kobject_put(kobj_ref);
kobj_create_err:
flx_serial_destroy(&sdev);
sdev_create_err:
kfree(sim_iomem);
return rv;
}
static void __exit mod_exit(void)
{
sysfs_remove_group(kobj_ref, &attr_group);
kobject_put(kobj_ref);
flx_serial_destroy(&sdev);
kfree(sim_iomem);
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_AUTHOR("Elena Zhivun <elena.zhivun@cern.ch>");
MODULE_DESCRIPTION("Simulated VMK180 serial device");
MODULE_LICENSE("GPL");
#ifndef FLX_FAKE_DEVICE_H
#define FLX_FAKE_DEVICE_H
#include <linux/kernel.h>
#include <linux/kobject.h>
#define register_read(offset) readl(offset)
#define register_write(offset, val) writel(val, offset)
ssize_t magic_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t magic_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
ssize_t recv_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t recv_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
ssize_t sent_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t can_recv_block_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t can_recv_block_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
ssize_t can_recv_byte_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t can_recv_byte_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
ssize_t can_send_block_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t can_send_block_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
ssize_t can_send_byte_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
ssize_t can_send_byte_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
#endif
\ No newline at end of file
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/vmalloc.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include "file_ops.h"
/* Read/Write access to the MMAPped registers */
#if defined(CONFIG_ARCH_ZYNQ) || defined(CONFIG_X86)
# define register_read(offset) readl(offset)
# define register_write(offset, val) writel(val, offset)
#else
# define register_read(offset) __raw_readl(offset)
# define register_write(offset, val) __raw_writel(val, offset)
#endif
// Send a single byte to the device
static inline void send_byte(struct flx_serial_device* sdev, char data) {
register_write(sdev->regs + sdev->reg_send, data);
}
// Receive a single byte
static inline char recv_byte(struct flx_serial_device* sdev) {
return (char)register_read(sdev->regs + sdev->reg_recv);
}
// check status flag
inline bool check_flag(void* __iomem reg, int bit_id) {
return ((1 << bit_id) & register_read(reg)) != 0;
}
static inline bool is_byte_available (struct flx_serial_device* sdev) {
return check_flag(sdev->regs + sdev->reg_status, BYTE_AVAILABLE);
}
static inline bool is_block_available (struct flx_serial_device* sdev) {
return check_flag(sdev->regs + sdev->reg_status, BLOCK_AVAILABLE);
}
static inline bool can_accept_byte (struct flx_serial_device* sdev) {
return check_flag(sdev->regs + sdev->reg_status, CAN_ACCEPT_BYTE);
}
static inline bool can_accept_block (struct flx_serial_device* sdev) {
return check_flag(sdev->regs + sdev->reg_status, CAN_ACCEPT_BLOCK);
}
extern spinlock_t device_table_lock;
extern bool flx_serial_ready;
extern struct flx_serial_device* device_table[];
// Verify that the device magic number is correct
inline bool correct_device_magic(struct flx_serial_device* sdev) {
return (register_read(sdev->regs + MAGIC_REG) == FLX_DEV_MAGIC);
}
// read system call handler
ssize_t device_read (struct file *filp, char __user *buf,
size_t count, loff_t *offset) {
struct flx_serial_device *sdev;
size_t bytes_to_recv;
char kbuf[BUF_SIZE];
DEFINE_WAIT(wait);
int i;
sdev = filp->private_data;
if (!sdev) {
pr_warn("sdev NULL ptr in %s\n", __FUNCTION__);
return -EINVAL;
}
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Incorrect MAGIC value in %s\n", __FUNCTION__);
return -EIO;
}
if (mutex_lock_interruptible(&sdev->rd_mutex))
return -ERESTARTSYS;
// wait for data if not available
while (!is_byte_available(sdev)) {
if (filp->f_flags & O_NONBLOCK) {
mutex_unlock(&sdev->rd_mutex);
return -EAGAIN;
}
// pr_info("No data to read, start timer %p\n", &sdev->rd_timer);
mod_timer(&sdev->rd_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
prepare_to_wait(&sdev->rd_queue, &wait, TASK_INTERRUPTIBLE);
if (!is_byte_available(sdev))
schedule();
finish_wait(&sdev->rd_queue, &wait);
if (signal_pending(current)) {
mutex_unlock(&sdev->rd_mutex);
return -ERESTARTSYS;
}
if (!is_byte_available(sdev)) {
mutex_unlock(&sdev->rd_mutex);
return -EAGAIN;
}
}
// copy data to user
if (is_block_available(sdev)) {
if (count <= BUF_SIZE) {
bytes_to_recv = count;
} else {
bytes_to_recv = BUF_SIZE;
}
} else {
bytes_to_recv = 1;
}
for (i=0; i<bytes_to_recv; i++) {
kbuf[i] = recv_byte(sdev);
}
if (copy_to_user(buf, kbuf, bytes_to_recv)) {
mutex_unlock(&sdev->rd_mutex);
return -EFAULT;
}
mutex_unlock(&sdev->rd_mutex);
return bytes_to_recv;
}
// write system call handler
ssize_t device_write (struct file *filp, const char __user *buf,
size_t count, loff_t *offset) {
struct flx_serial_device *sdev;
size_t bytes_to_send;
char kbuf[BUF_SIZE];
DEFINE_WAIT(wait);
int i;
// pr_info("Invoked %s (%ld bytes)\n", __FUNCTION__, count);
sdev = filp->private_data;
if (!sdev) {
pr_warn("sdev NULL ptr in %s\n", __FUNCTION__);
return -EINVAL;
}
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Incorrect MAGIC value in %s\n", __FUNCTION__);
return -EIO;
}
if (mutex_lock_interruptible(&sdev->wr_mutex))
return -ERESTARTSYS;
// wait for free buffer space if not available
while (!can_accept_byte(sdev)) {
if (filp->f_flags & O_NONBLOCK) {
mutex_unlock(&sdev->wr_mutex);
return -EAGAIN;
}
// pr_info("Can't write data, start timer %p\n", &sdev->wr_timer);
mod_timer(&sdev->wr_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
prepare_to_wait(&sdev->wr_queue, &wait, TASK_INTERRUPTIBLE);
if (!can_accept_byte(sdev))
schedule();
finish_wait(&sdev->wr_queue, &wait);
if (signal_pending(current)) {
mutex_unlock(&sdev->wr_mutex);
return -ERESTARTSYS;
}
if (!can_accept_byte(sdev)) {
mutex_unlock(&sdev->wr_mutex);
return -EAGAIN;
}
}
// send off the data
if (can_accept_block(sdev)) {
if (count <= BUF_SIZE) {
bytes_to_send = count;
} else {
bytes_to_send = BUF_SIZE;
}
} else {
bytes_to_send = 1;
}
if (copy_from_user(kbuf, buf, bytes_to_send)) {
mutex_unlock(&sdev->wr_mutex);
return -EFAULT;
}
for (i=0; i<bytes_to_send; i++) {
send_byte(sdev, kbuf[i]);
}
mutex_unlock(&sdev->wr_mutex);
return bytes_to_send;
}
// open system call handler
int device_open (struct inode *inode, struct file *filp) {
struct flx_serial_device *sdev;
int minor;
//pr_info("Called function %s\n", __FUNCTION__);
// sanity check: minor is valid
minor = iminor(inode);
if (minor < 0 || minor >= MINOR_COUNT) {
pr_err("Invalid device minor %d\n", minor);
return -EIO;
}
spin_lock(&device_table_lock);
// sanity check: the driver is not in the process of being shutdown
if (!flx_serial_ready) {
spin_unlock(&device_table_lock);
pr_err("flx_serial_device is not ready\n");
return -EIO;
}
// sanity check: this minor corresponds to a device
sdev = device_table[minor];
if (!sdev) {
spin_unlock(&device_table_lock);
pr_err("Couldn't find flx_serial_device with minor %d\n", minor);
return -EIO;
}
spin_unlock(&device_table_lock);
// sanity check: sdev MAGIC is correct
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Incorrect sdev MAGIC value in %s\n", __FUNCTION__);
return -EIO;
}
// sanity check: device MAGIC is correct
if (!correct_device_magic(sdev)) {
pr_err("Incorrect device MAGIC value in %s\n", __FUNCTION__);
return -EIO;
}
// check if it's already open
if (!atomic_dec_and_test(&sdev->num_available)) {
atomic_inc(&sdev->num_available);
return -EBUSY;
}
// increase reference counter on the module that registered this device
if (!try_module_get(sdev->owner)) {
return -EINVAL;
}
filp->private_data = sdev;
// pr_info("Successfully opened %s\n", filp->f_path.dentry->d_iname);
return 0;
}
// close system call handler
int device_release (struct inode *inode, struct file *filp) {
struct flx_serial_device *sdev;
sdev = filp->private_data;
//pr_info("Closing %s\n", filp->f_path.dentry->d_iname);
if (!sdev) {
pr_err("sdev is NULL ptr in %s\n", __FUNCTION__);
return -EINVAL;
}
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_warn("Incorrect MAGIC value in %s\n", __FUNCTION__);
}
spin_lock_bh(&sdev->timer_deactivation_lock);
sdev->timers_deacivated = true;
spin_unlock_bh(&sdev->timer_deactivation_lock);
del_timer_sync(&sdev->rd_timer);
del_timer_sync(&sdev->wr_timer);
del_timer_sync(&sdev->rw_timer);
sdev->timers_deacivated = false;
module_put(sdev->owner);
atomic_inc(&sdev->num_available);
return 0;
}
void timer_poll_read(struct timer_list * poll_timer)
{
struct flx_serial_device *sdev;
sdev = container_of(poll_timer, struct flx_serial_device, rd_timer);
// pr_info_ratelimited("In timer %s\n", __FUNCTION__);
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Invalid sdev struct MAGIC in %s, sdev is %p, timer is %p\n",
__FUNCTION__, sdev, poll_timer);
return;
}
if (is_byte_available(sdev)) {
wake_up_interruptible(&sdev->rd_queue);
} else {
spin_lock_bh(&sdev->timer_deactivation_lock);
if (!sdev->timers_deacivated) {
mod_timer(poll_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
}
spin_unlock_bh(&sdev->timer_deactivation_lock);
}
}
void timer_poll_write(struct timer_list * poll_timer)
{
struct flx_serial_device *sdev;
sdev = container_of(poll_timer, struct flx_serial_device, wr_timer);
// pr_info_ratelimited("In timer %s\n", __FUNCTION__);
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Invalid sdev struct MAGIC in %s, sdev is %p, timer is %p\n",
__FUNCTION__, sdev, poll_timer);
return;
}
if (can_accept_byte(sdev)) {
wake_up_interruptible(&sdev->wr_queue);
} else {
spin_lock_bh(&sdev->timer_deactivation_lock);
if (!sdev->timers_deacivated) {
mod_timer(poll_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
}
spin_unlock_bh(&sdev->timer_deactivation_lock);
}
}
void timer_poll_rw(struct timer_list * poll_timer)
{
struct flx_serial_device *sdev;
int status;
// pr_info_ratelimited("In timer %s\n", __FUNCTION__);
sdev = container_of(poll_timer, struct flx_serial_device, rw_timer);
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Invalid sdev struct MAGIC in %s, sdev is %p, timer is %p\n",
__FUNCTION__, sdev, poll_timer);
return;
}
status = register_read(sdev->regs + sdev->reg_status);
// pr_info_ratelimited("Status register value %#010x\n", status);
if ((status & (1 << CAN_ACCEPT_BYTE)) != 0 || (status & (1 << BYTE_AVAILABLE)) != 0) {
wake_up_interruptible(&sdev->rw_queue);
} else {
spin_lock_bh(&sdev->timer_deactivation_lock);
if (!sdev->timers_deacivated) {
mod_timer(poll_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
}
spin_unlock_bh(&sdev->timer_deactivation_lock);
}
}
unsigned int device_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask;
struct flx_serial_device *sdev;
int status;
mask = 0;
sdev = filp->private_data;
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("Invalid sdev struct MAGIC in %s, sdev is %p\n", __FUNCTION__, sdev);
return -EFAULT;
}
// if the device is available for both read and write, there is no need to sleep
status = register_read(sdev->regs + sdev->reg_status);
if ((status & (1 << CAN_ACCEPT_BYTE)) != 0 && (status & (1 << BYTE_AVAILABLE)) != 0) {
mask |= POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
return mask;
}
// no data available, go to sleep
mod_timer(&sdev->rw_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
poll_wait(filp, &sdev->rw_queue, wait);
status = register_read(sdev->regs + sdev->reg_status);
if ((status & (1 << CAN_ACCEPT_BYTE)) != 0 || (status & (1 << BYTE_AVAILABLE)) != 0) {
if ((status & (1 << BYTE_AVAILABLE)) != 0) {
mask |= POLLIN | POLLRDNORM;
}
if ((status & (1 << CAN_ACCEPT_BYTE)) != 0) {
mask |= (POLLOUT | POLLWRNORM);
}
}
return mask;
}
\ No newline at end of file
#ifndef FLX_SERIAL_FOPS_H
#define FLX_SERIAL_FOPS_H
#include "flx_serial_cdev.h"
#define BUF_SIZE 256
#define HOST_ADDR_SEND 0x00
#define HOST_ADDR_RECV 0x04
#define HOST_ADDR_STATUS 0x8
#define TARGET_ADDR_SEND 0x0C
#define TARGET_ADDR_RECV 0x10
#define TARGET_ADDR_STATUS 0x14
#define MAGIC_REG 0x18
#define CAN_ACCEPT_BLOCK 3
#define CAN_ACCEPT_BYTE 2
#define BLOCK_AVAILABLE 1
#define BYTE_AVAILABLE 0
ssize_t device_read (struct file *filp, char __user *buf,
size_t count, loff_t *offset);
ssize_t device_write (struct file *filp, const char __user *buf,
size_t count, loff_t *offset);
int device_open (struct inode *inode, struct file *filp);
int device_release (struct inode *inode, struct file *filp);
void timer_poll_read(struct timer_list * poll_timer);
void timer_poll_write(struct timer_list * poll_timer);
void timer_poll_rw(struct timer_list * poll_timer);
unsigned int device_poll(struct file *filp, struct poll_table_struct *wait);
inline bool correct_device_magic(struct flx_serial_device* sdev);
#endif
\ No newline at end of file
/*
This is a demonstration for establishing basic communication
between VMK180 and host PC over CPM. This is the common
serial device driver.
This driver was made to demonstrate basic serial device
communication between VMK180 and host over PCIe. It's not
intended for production since the performance has not been
optimized.
Elena Zhivun <elena.zhivun@cern.ch>
*/
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/timer.h>
#include "flx_serial_cdev.h"
#include "file_ops.h"
#define DEVICE_NAME "flx_serial"
static int major;
static struct class *flx_serial_class;
DEFINE_SPINLOCK(device_table_lock);
struct flx_serial_device* device_table[MINOR_COUNT];
bool flx_serial_ready = false;
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release,
.poll = device_poll,
.owner = THIS_MODULE
};
/*
Adds the device into the device table
Returns the the position of the added revice if successful,
return -1 if the table is full or creation of devices is not allowed
*/
static int add_minor(struct flx_serial_device* sdev) {
int i;
if (!sdev) {
return -1;
}
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_err("sdev MAGIC is invalid in add_minor\n");
return -1;
}
spin_lock(&device_table_lock);
if (!flx_serial_ready) {
spin_unlock(&device_table_lock);
return -1;
}
for (i = 0; i < MINOR_COUNT-1; i++) {
if (device_table[i] == NULL) {
device_table[i] = sdev;
spin_unlock(&device_table_lock);
return i;
}
}
spin_unlock(&device_table_lock);
return -1;
}
// Create a serial device instance
int flx_serial_create(struct flx_serial_device* sdev,
void* __iomem base_address, int role, struct module *owner) {
int minor;
struct cdev* cdev;
// pr_info("Creating serial device: start\n");
if (!sdev) {
pr_err("Can't add cdev - sdev is NULL\n");
return -1;
}
cdev = &sdev->cdev;
cdev_init(cdev, &fops);
// pr_info("Figuring out device role\n");
if (role == ROLE_HOST) {
sdev->reg_send = HOST_ADDR_SEND;
sdev->reg_recv = HOST_ADDR_RECV;
sdev->reg_status = HOST_ADDR_STATUS;
} else if (role == ROLE_TARGET) {
sdev->reg_send = TARGET_ADDR_SEND;
sdev->reg_recv = TARGET_ADDR_RECV;
sdev->reg_status = TARGET_ADDR_STATUS;
} else {
pr_warn("Unknown sdev role: %d\n", sdev->role);
return -1;
}
// pr_info("Filling out device struct\n");
sdev->magic = FLX_STRUCT_MAGIC;
sdev->regs = base_address;
sdev->role = role;
sdev->owner = owner;
atomic_set(&sdev->num_available, 1);
mutex_init(&sdev->rd_mutex);
mutex_init(&sdev->wr_mutex);
spin_lock_init(&sdev->timer_deactivation_lock);
init_waitqueue_head(&sdev->rd_queue);
init_waitqueue_head(&sdev->wr_queue);
init_waitqueue_head(&sdev->rw_queue);
timer_setup(&sdev->rd_timer, timer_poll_read, 0);
timer_setup(&sdev->wr_timer, timer_poll_write, 0);
timer_setup(&sdev->rw_timer, timer_poll_rw, 0);
sdev->timers_deacivated = false;
// for (i=0; i<7; i++) {
// pr_info("Register %d addr=(%p), value: %#010X\n", i,
// (void*)(sdev->regs + 4*i), register_read(sdev->regs + 4*i));
// }
if (!correct_device_magic(sdev)) {
pr_err("Invalid device: incorrect magic number\n");
return -1;
}
minor = add_minor(sdev);
sdev->minor = minor;
if (minor < 0) {
pr_err("Can't create device minor\n");
return -1;
}
//pr_info("Creating serial device\n");
device_create(flx_serial_class, NULL, MKDEV(major, minor), NULL,
DEVICE_NAME"%d", minor);
// pr_info("Done creating the device!\n");
return 0;
}
// Remove a serial device instance
void flx_serial_destroy(struct flx_serial_device* sdev) {
if (!sdev) {
pr_warn("sdev = NULL in flx_serial_destroy\n");
return;
}
if (sdev->magic != FLX_STRUCT_MAGIC) {
pr_warn("sdev struct MAGIC is invalid in flx_serial_destroy\n");
return;
}
spin_lock_bh(&sdev->timer_deactivation_lock);
sdev->timers_deacivated = true;
spin_unlock_bh(&sdev->timer_deactivation_lock);
del_timer_sync(&sdev->rd_timer);
del_timer_sync(&sdev->wr_timer);
del_timer_sync(&sdev->rw_timer);
device_destroy(flx_serial_class, MKDEV(major, sdev->minor));
spin_lock(&device_table_lock);
device_table[sdev->minor] = NULL;
spin_unlock(&device_table_lock);
}
// Register the character device
int __init flx_cdev_init(void) {
int rv;
memset(device_table, 0x00, sizeof(struct flx_serial_device*) * MINOR_COUNT);
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) {
pr_err("Can't allocate major number: %d\n", rv);
goto fail_alloc_major;
}
flx_serial_class = class_create(THIS_MODULE, DEVICE_NAME);
if (!flx_serial_class) {
pr_err("Failed to register %s class\n", DEVICE_NAME);
rv = -1;
goto fail_class_create;
}
pr_info("flx_serial device major is %d\n", major);
pr_info("Successfully registered class %s\n", DEVICE_NAME);
flx_serial_ready = true;
return 0;
fail_class_create:
unregister_chrdev(major, DEVICE_NAME);
fail_alloc_major:
return rv;
}
// Unegister the character device
void __exit flx_cdev_exit(void) {
class_destroy(flx_serial_class);
unregister_chrdev(major, DEVICE_NAME);
}
MODULE_AUTHOR("Elena Zhivun <elena.zhivun@cern.ch>");
MODULE_DESCRIPTION("Character device for communicating with VMK180 over CPM");
module_init(flx_cdev_init);
module_exit(flx_cdev_exit);
EXPORT_SYMBOL(flx_serial_create);
EXPORT_SYMBOL(flx_serial_destroy);
MODULE_LICENSE("GPL");
#ifndef FLX_SERIAL_CDEV_H
#define FLX_SERIAL_CDEV_H
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#define ROLE_HOST 0
#define ROLE_TARGET 1
#define MIN_MMAP_SIZE 0x20
#define MAJOR_COUNT 1
#define MINOR_COUNT 256
#define POLL_PERIOD_MS 5
#define FLX_DEV_MAGIC 0xCECABABAUL
#define FLX_STRUCT_MAGIC 0xF31BA7AUL
struct flx_serial_device {
unsigned long magic; /* structure ID for sanity checks */
void __iomem *regs;
struct cdev cdev;
struct module *owner;
int role;
const char *mod_name; /* name of module owning the dev */
struct mutex rd_mutex; // read syscall mutex
struct mutex wr_mutex; // write syscall mutex
wait_queue_head_t rd_queue;
wait_queue_head_t wr_queue;
wait_queue_head_t rw_queue;
struct timer_list rd_timer;
struct timer_list wr_timer;
struct timer_list rw_timer;
atomic_t num_available;
spinlock_t timer_deactivation_lock;
bool timers_deacivated;
unsigned int reg_send;
unsigned int reg_recv;
unsigned int reg_status;
unsigned int minor;
};
#endif
\ No newline at end of file
/*
This is an minimal example driver for host side
to demonstrate serial communication over
PCIe using only a few registers from a separate BAR.
When module is loaded:
1. Find vmk180 PCIe card (vendor=0x10ee, device=0xb03f)
2. Enable the PCIe device, reserve and map the BAR corresponding
to the CPM AXI bridge (set by bar_idx parameter)
3. Create /dev/flx_serial0 character device(s)
Elena Zhivun <elena.zhivun@cern.ch>
*/
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include "pcie_serial.h"
#include "flx_serial_cdev.h"
extern int flx_serial_create(struct flx_serial_device* sdev,
void* __iomem base_address, int role, struct module *owner);
extern void flx_serial_destroy(struct flx_serial_device* sdev);
#define DRV_MODULE_NAME "flx_serial_host"
// TODO: make this work for multiple devices
// Which bar number the AXI serial device is mapped to
static int bar_idx = 1;
// Which offset the AXI serial device is found at
static uint dev_base_addr = 0x0;
// List of FELIX devices
static LIST_HEAD(flx_devices);
static int configure_pcie_device(struct pci_dev *pdev);
static void deconfigure_device(struct pcie_flx_serial_device *fdev);
static int map_single_bar(struct pcie_flx_serial_device *fdev, int idx);
static int flx_serial_init(void)
{
int rv;
struct pci_dev *pdev;
pdev = NULL;
pr_info("Loading %s module\n", DRV_MODULE_NAME);
if ( (pdev = pci_get_device(0x10ee, 0xb03f, pdev)) ) {
pr_info("Found %s", pci_name(pdev));
rv = configure_pcie_device(pdev);
if (rv < 0) {
pr_warn("Failed to configure %s\n", pci_name(pdev));
}
} else {
pr_err("Couldn't find VMK180\n");
rv = -ENODEV;
goto err_out;
}
return 0;
err_out:
return rv;
}
static void flx_serial_exit(void)
{
struct pcie_flx_serial_device *fdev;
pr_info("Removing %s module\n", DRV_MODULE_NAME);
if (!list_empty(&flx_devices)) {
list_for_each_entry(fdev, &flx_devices, list_head) {
if (fdev->magic != FLX_PCIE_MAGIC) {
pr_err("Incorrect MAGIC value in flx_serial_exit()\n");
return;
}
deconfigure_device(fdev);
}
}
}
static int configure_pcie_device(struct pci_dev *pdev)
{
int rv;
struct pcie_flx_serial_device *fdev;
if (!pdev) {
pr_err("Invalid PCIe device structure\n");
return -EFAULT;
}
pr_info("Initializing the PCIe device\n");
fdev = kzalloc(sizeof(struct pcie_flx_serial_device), GFP_KERNEL);
if (!fdev)
return -ENOMEM;
spin_lock_init(&fdev->lock);
fdev->magic = FLX_PCIE_MAGIC;
fdev->pdev = pdev;
fdev->bar_idx = bar_idx;
fdev->sdev.mod_name = DRV_MODULE_NAME;
INIT_LIST_HEAD(&fdev->list_head);
pr_info("Enabling PCIe device\n");
rv = pci_enable_device(pdev);
if (rv) {
pr_err("pci_enable_device() failed, %d.\n", rv);
goto err_enable;
}
// reserve the selected bar for this driver
pr_info("Reserving BAR memory\n");
rv = pci_request_region(pdev, bar_idx, DRV_MODULE_NAME);
if (rv) {
pr_err("pci_request_region() failed (%d).\n", rv);
goto err_region;
}
pr_info("Mapping BAR memory\n");
rv = map_single_bar(fdev, bar_idx);
if (rv < 0) {
goto err_map;
} else if (rv < MIN_MMAP_SIZE) {
pr_err("Mapped region is too small (%d bytes < %d).\n",
rv, MIN_MMAP_SIZE);
goto err_cdev;
}
pr_info("Creating serial device\n");
rv = flx_serial_create(&fdev->sdev, fdev->bar + dev_base_addr,
ROLE_HOST, THIS_MODULE);
if (rv < 0) {
goto err_cdev;
}
// device structure
list_add(&fdev->list_head, &flx_devices);
return 0;
err_cdev:
pr_info("Unmapping BAR memory\n");
pci_iounmap(pdev, fdev->bar);
err_map:
pr_info("Disabling PCIe device\n");
pci_disable_device(pdev);
pci_release_region(pdev, bar_idx);
err_enable:
pr_err("pdev 0x%p, err %d.\n", pdev, rv);
kfree(fdev);
return rv;
err_region:
pci_disable_device(pdev);
goto err_enable;
}
// Unregister and deconfigure a felix serial device
static void deconfigure_device(struct pcie_flx_serial_device *fdev) {
struct pci_dev * pdev;
if (!fdev) {
pr_err("felix_flx_serial_device ptr is NULL\n");
return;
}
if (fdev->magic != FLX_PCIE_MAGIC) {
pr_err("Incorrect MAGIC value in deconfigure_device()\n");
return;
}
pdev = fdev->pdev;
if (!pdev) {
pr_err("pci_dev ptr is NULL\n");
goto err_exit;
}
if (fdev->bar) {
pci_iounmap(pdev, fdev->bar);
} else {
pr_err("BAR memory is not mapped\n");
}
flx_serial_destroy(&fdev->sdev);
pci_disable_device(pdev);
pci_release_region(pdev, bar_idx);
err_exit:
list_del(&fdev->list_head);
kfree(fdev);
}
// Map BAR containing the serial communication registers
static int map_single_bar(struct pcie_flx_serial_device *fdev, int idx)
{
struct pci_dev *pdev = fdev->pdev;
resource_size_t bar_start = pci_resource_start(pdev, idx);
resource_size_t bar_len = pci_resource_len(pdev, idx);
resource_size_t map_len = bar_len;
fdev->bar = NULL;
/* do not map BARs with length 0. Note that start MAY be 0! */
if (!bar_len) {
pr_info("BAR #%d is not present\n", idx);
return -1;
}
/* BAR size exceeds maximum desired mapping? */
if (bar_len > INT_MAX) {
pr_info("Limit BAR %d mapping from %llu to %d bytes\n", idx,
(u64)bar_len, INT_MAX);
map_len = (resource_size_t)INT_MAX;
}
/*
* map the full device memory or IO region into kernel virtual
* address space
*/
fdev->bar = pci_iomap(pdev, idx, map_len);
if (!fdev->bar) {
pr_info("Could not map BAR %d.\n", idx);
return -1;
}
pr_info("BAR%d at 0x%llx mapped at 0x%p, length=%llu(/%llu)\n", idx,
(u64)bar_start, fdev->bar, (u64)map_len, (u64)bar_len);
return (int)map_len;
}
MODULE_AUTHOR("Elena Zhivun <elena.zhivun@cern.ch>");
MODULE_DESCRIPTION("Example driver for communicating with VMK180 over CPM");
module_init(flx_serial_init);
module_exit(flx_serial_exit);
module_param(bar_idx, int, S_IRUSR);
MODULE_PARM_DESC(bar_idx, "BAR of the serial AXI device");
module_param(dev_base_addr, uint, S_IRUSR);
MODULE_PARM_DESC(dev_base_addr, "Base offset of the serial AXI device");
MODULE_LICENSE("GPL");
#ifndef FLX_SERIAL_H
#define FLX_SERIAL_H
#include <linux/module.h>
#include "flx_serial_cdev.h"
#define FLX_PCIE_MAGIC 0xDED133D0UL
struct pcie_flx_serial_device {
unsigned long magic; /* structure ID for sanity checks */
struct list_head list_head;
struct pci_dev *pdev; /* pci device struct from probe() */
void __iomem *bar; /* addresses for the relevant BAR */
int bar_idx;
spinlock_t lock; /* protects concurrent access */
unsigned int flags;
struct flx_serial_device sdev;
};
#endif /* FLX_SERIAL_H */
\ No newline at end of file
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/platform_device.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/kobject.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include "flx_serial_cdev.h"
#include "file_ops.h"
#include "fake_device.h"
extern void * sim_iomem;
void set_flag(void* reg, int bit_id, bool value) {
int reg_value;
reg_value = register_read(reg);
if (value) {
reg_value |= (1 << bit_id);
} else {
reg_value &= ~(1 << bit_id);
}
// pr_info("Set control register to %#04x\n", reg_value);
register_write(reg, reg_value);
}
bool check_flag(void* reg, int bit_id) {
return ((1 << bit_id) & register_read(reg)) != 0;
}
// Show and store MAGIC number
ssize_t magic_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%#010x", register_read(sim_iomem + MAGIC_REG));
}
ssize_t magic_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int value;
unsigned int result;
result = sscanf(buf, "%x", &value);
if (!result)
return count;
register_write(sim_iomem + MAGIC_REG, value);
return count;
}
// Show and store byte to receive
ssize_t recv_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%#04x", register_read(sim_iomem + HOST_ADDR_RECV) & 0xFF);
}
ssize_t recv_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int value;
unsigned int result;
result = sscanf(buf, "%x", &value);
if (!result)
return count;
register_write(sim_iomem + HOST_ADDR_RECV, value & 0xFF);
// pr_info("Set recv register to %#010x\n", value);
return count;
}
// Show last byte sent into the device
ssize_t sent_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%#04x", register_read(sim_iomem + HOST_ADDR_SEND) & 0xFF);
}
// can_receive_byte status flag
ssize_t can_recv_byte_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
int flag;
flag = (int) check_flag(sim_iomem + HOST_ADDR_STATUS, BYTE_AVAILABLE);
return sprintf(buf, "%d", flag);
}
ssize_t can_recv_byte_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int value;
unsigned int result;
result = sscanf(buf, "%d", &value);
if (!result)
return count;
set_flag(sim_iomem + HOST_ADDR_STATUS, BYTE_AVAILABLE, (bool)value);
return count;
}
// can_receive_block status flag
ssize_t can_recv_block_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
int flag;
flag = (int) check_flag(sim_iomem + HOST_ADDR_STATUS, BLOCK_AVAILABLE);
return sprintf(buf, "%d", flag);
}
ssize_t can_recv_block_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int value;
unsigned int result;
result = sscanf(buf, "%d", &value);
if (!result)
return count;
set_flag(sim_iomem + HOST_ADDR_STATUS, BLOCK_AVAILABLE, (bool)value);
return count;
}
// can_send_byte status flag
ssize_t can_send_byte_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
int flag;
flag = (int) check_flag(sim_iomem + HOST_ADDR_STATUS, CAN_ACCEPT_BYTE);
return sprintf(buf, "%d", flag);
}
ssize_t can_send_byte_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int value;
unsigned int result;
result = sscanf(buf, "%d", &value);
if (!result)
return count;
set_flag(sim_iomem + HOST_ADDR_STATUS, CAN_ACCEPT_BYTE, (bool)value);
return count;
}
// can_send_block status flag
ssize_t can_send_block_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
int flag;
flag = (int) check_flag(sim_iomem + HOST_ADDR_STATUS, CAN_ACCEPT_BLOCK);
return sprintf(buf, "%d", flag);
}
ssize_t can_send_block_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int value;
unsigned int result;
result = sscanf(buf, "%d", &value);
if (!result)
return count;
set_flag(sim_iomem + HOST_ADDR_STATUS, CAN_ACCEPT_BLOCK, (bool)value);
return count;
}
\ No newline at end of file
/*
This is an minimal example driver for vmk180 side
to demonstrate minimal serial communication over PCIe.
Device tree entry example:
&amba {
pcie-serial@20100050000 {
#address-cells = <0x02>;
#size-cells = <0x02>;
reg = <0x201 0x00050000 0x00 0x100>;
compatible = "bnl,vmk180-serial-example";
status = "okay";
};
};
Elena Zhivun <elena.zhivun@cern.ch>
*/
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/platform_device.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include "flx_serial_cdev.h"
#define DRV_MODULE_NAME "flx_serial_target"
extern int flx_serial_create(struct flx_serial_device* sdev,
void* __iomem base_address, int role, struct module *owner);
extern void flx_serial_destroy(struct flx_serial_device* sdev);
static int flx_serial_probe(struct platform_device *pdev)
{
void * __iomem base;
struct flx_serial_device *sdev;
int rv;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base)) {
dev_err(&pdev->dev, "failed to ioremap memory resource\n");
return -ENODEV;
}
sdev = kzalloc(sizeof(struct flx_serial_device), GFP_KERNEL);
if (!sdev) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENODEV;
}
rv = flx_serial_create(sdev, base, ROLE_TARGET, THIS_MODULE);
if (rv < 0) {
dev_err(&pdev->dev, "failed to create serial device\n");
return -ENODEV;
}
platform_set_drvdata(pdev, sdev);
return 0;
}
static int flx_serial_remove(struct platform_device *pdev)
{
struct flx_serial_device *sdev;
sdev = platform_get_drvdata(pdev);
if (!sdev) {
dev_warn(&pdev->dev, "failed to free serial device data\n");
return -1;
}
flx_serial_destroy(sdev);
kfree(sdev);
return 0;
}
static const struct of_device_id flx_serial_of_match[] = {
{ .compatible = "bnl,vmk180-serial-example", .data = NULL },
{ /* end of table */ }
};
MODULE_DEVICE_TABLE(of, flx_serial_of_match);
static struct platform_driver flx_serial_driver = {
.driver = {
.name = DRV_MODULE_NAME,
.owner = THIS_MODULE,
.of_match_table = flx_serial_of_match,
},
.probe = flx_serial_probe,
.remove = flx_serial_remove,
};
module_platform_driver(flx_serial_driver);
MODULE_AUTHOR("Elena Zhivun <elena.zhivun@cern.ch>");
MODULE_DESCRIPTION("Example driver for communicating with VMK180 over CPM");
MODULE_LICENSE("GPL");
proc init { cellpath otherInfo } {
set cell_handle [get_bd_cells $cellpath]
set all_busif [get_bd_intf_pins $cellpath/*]
set axi_standard_param_list [list ID_WIDTH AWUSER_WIDTH ARUSER_WIDTH WUSER_WIDTH RUSER_WIDTH BUSER_WIDTH]
set full_sbusif_list [list ]
foreach busif $all_busif {
if { [string equal -nocase [get_property MODE $busif] "slave"] == 1 } {
set busif_param_list [list]
set busif_name [get_property NAME $busif]
if { [lsearch -exact -nocase $full_sbusif_list $busif_name ] == -1 } {
continue
}
foreach tparam $axi_standard_param_list {
lappend busif_param_list "C_${busif_name}_${tparam}"
}
bd::mark_propagate_only $cell_handle $busif_param_list
}
}
}
proc pre_propagate {cellpath otherInfo } {
set cell_handle [get_bd_cells $cellpath]
set all_busif [get_bd_intf_pins $cellpath/*]
set axi_standard_param_list [list ID_WIDTH AWUSER_WIDTH ARUSER_WIDTH WUSER_WIDTH RUSER_WIDTH BUSER_WIDTH]
foreach busif $all_busif {
if { [string equal -nocase [get_property CONFIG.PROTOCOL $busif] "AXI4"] != 1 } {
continue
}
if { [string equal -nocase [get_property MODE $busif] "master"] != 1 } {
continue
}
set busif_name [get_property NAME $busif]
foreach tparam $axi_standard_param_list {
set busif_param_name "C_${busif_name}_${tparam}"
set val_on_cell_intf_pin [get_property CONFIG.${tparam} $busif]
set val_on_cell [get_property CONFIG.${busif_param_name} $cell_handle]
if { [string equal -nocase $val_on_cell_intf_pin $val_on_cell] != 1 } {
if { $val_on_cell != "" } {
set_property CONFIG.${tparam} $val_on_cell $busif
}
}
}
}
}
proc propagate {cellpath otherInfo } {
set cell_handle [get_bd_cells $cellpath]
set all_busif [get_bd_intf_pins $cellpath/*]
set axi_standard_param_list [list ID_WIDTH AWUSER_WIDTH ARUSER_WIDTH WUSER_WIDTH RUSER_WIDTH BUSER_WIDTH]
foreach busif $all_busif {
if { [string equal -nocase [get_property CONFIG.PROTOCOL $busif] "AXI4"] != 1 } {
continue
}
if { [string equal -nocase [get_property MODE $busif] "slave"] != 1 } {
continue
}
set busif_name [get_property NAME $busif]
foreach tparam $axi_standard_param_list {
set busif_param_name "C_${busif_name}_${tparam}"
set val_on_cell_intf_pin [get_property CONFIG.${tparam} $busif]
set val_on_cell [get_property CONFIG.${busif_param_name} $cell_handle]
if { [string equal -nocase $val_on_cell_intf_pin $val_on_cell] != 1 } {
#override property of bd_interface_net to bd_cell -- only for slaves. May check for supported values..
if { $val_on_cell_intf_pin != "" } {
set_property CONFIG.${busif_param_name} $val_on_cell_intf_pin $cell_handle
}
}
}
}
}
OPTION psf_version = 2.1;
BEGIN DRIVER vmk180_serial_demo
OPTION supported_peripherals = (vmk180_serial_demo);
OPTION copyfiles = all;
OPTION VERSION = 1.0;
OPTION NAME = vmk180_serial_demo;
END DRIVER
proc generate {drv_handle} {
xdefine_include_file $drv_handle "xparameters.h" "vmk180_serial_demo" "NUM_INSTANCES" "DEVICE_ID" "C_S00_AXI_BASEADDR" "C_S00_AXI_HIGHADDR"
}
COMPILER=
ARCHIVER=
CP=cp
COMPILER_FLAGS=
EXTRA_COMPILER_FLAGS=
LIB=libxil.a
RELEASEDIR=../../../lib
INCLUDEDIR=../../../include
INCLUDES=-I./. -I${INCLUDEDIR}
INCLUDEFILES=*.h
LIBSOURCES=*.c
OUTS = *.o
libs:
echo "Compiling vmk180_serial_demo..."
$(COMPILER) $(COMPILER_FLAGS) $(EXTRA_COMPILER_FLAGS) $(INCLUDES) $(LIBSOURCES)
$(ARCHIVER) -r ${RELEASEDIR}/${LIB} ${OUTS}
make clean
include:
${CP} $(INCLUDEFILES) $(INCLUDEDIR)
clean:
rm -rf ${OUTS}
/***************************** Include Files *******************************/
#include "vmk180_serial_demo.h"
/************************** Function Definitions ***************************/
#ifndef VMK180_SERIAL_DEMO_H
#define VMK180_SERIAL_DEMO_H
/****************** Include Files ********************/
#include "xil_types.h"
#include "xstatus.h"
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG0_OFFSET 0
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG1_OFFSET 4
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG2_OFFSET 8
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG3_OFFSET 12
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG4_OFFSET 16
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG5_OFFSET 20
#define VMK180_SERIAL_DEMO_S00_AXI_SLV_REG6_OFFSET 24
/**************************** Type Definitions *****************************/
/**
*
* Write a value to a VMK180_SERIAL_DEMO register. A 32 bit write is performed.
* If the component is implemented in a smaller width, only the least
* significant data is written.
*
* @param BaseAddress is the base address of the VMK180_SERIAL_DEMOdevice.
* @param RegOffset is the register offset from the base to write to.
* @param Data is the data written to the register.
*
* @return None.
*
* @note
* C-style signature:
* void VMK180_SERIAL_DEMO_mWriteReg(u32 BaseAddress, unsigned RegOffset, u32 Data)
*
*/
#define VMK180_SERIAL_DEMO_mWriteReg(BaseAddress, RegOffset, Data) \
Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
/**
*
* Read a value from a VMK180_SERIAL_DEMO register. A 32 bit read is performed.
* If the component is implemented in a smaller width, only the least
* significant data is read from the register. The most significant data
* will be read as 0.
*
* @param BaseAddress is the base address of the VMK180_SERIAL_DEMO device.
* @param RegOffset is the register offset from the base to write to.
*
* @return Data is the data from the register.
*
* @note
* C-style signature:
* u32 VMK180_SERIAL_DEMO_mReadReg(u32 BaseAddress, unsigned RegOffset)
*
*/
#define VMK180_SERIAL_DEMO_mReadReg(BaseAddress, RegOffset) \
Xil_In32((BaseAddress) + (RegOffset))
/************************** Function Prototypes ****************************/
/**
*
* Run a self-test on the driver/device. Note this may be a destructive test if
* resets of the device are performed.
*
* If the hardware system is not built correctly, this function may never
* return to the caller.
*
* @param baseaddr_p is the base address of the VMK180_SERIAL_DEMO instance to be worked on.
*
* @return
*
* - XST_SUCCESS if all self-test code passed
* - XST_FAILURE if any self-test code failed
*
* @note Caching must be turned off for this function to work.
* @note Self test may fail if data memory and device are not on the same bus.
*
*/
XStatus VMK180_SERIAL_DEMO_Reg_SelfTest(void * baseaddr_p);
#endif // VMK180_SERIAL_DEMO_H