diff --git a/Makefile b/Makefile index efa7019..53194b2 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ KDIR ?= /lib/modules/$(shell uname -r)/build -obj-m += ccat.o ccat_netdev.o ccat_gpio.o ccat_sram.o ccat_systemtime.o ccat_update.o +obj-m += ccat.o ccat_netdev.o ccat_gpio.o ccat_sram.o ccat_systemtime.o ccat_update.o ccat_esc.o ccat_irq.o ccat-y := module.o ccat_netdev-y := netdev.o ccat_gpio-y := gpio.o ccat_sram-y := sram.o ccat_systemtime-y := systemtime.o ccat_update-y := update.o +ccat_esc-y := esc.o +ccat_irq-y := irq.o #ccflags-y := -DDEBUG ccflags-y += -D__CHECK_ENDIAN__ @@ -22,7 +24,12 @@ install: - rmmod ccat_netdev - rmmod ccat make -C $(KDIR) M=$(CURDIR) modules_install + cp etc/modprobe.d/ccat.conf /etc/modprobe.d/ + cp etc/udev/rules.d/991-ccat.rules /etc/udev/rules.d/ + depmod -a modprobe ccat + modprobe ccat_esc + modprobe ccat_irq modprobe ccat_netdev modprobe ccat_gpio modprobe ccat_sram diff --git a/README.md b/README.md index 257845e..64e0460 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,15 @@ https://infosys.beckhoff.com/english.php?content=../content/1033/ethercatsystem/ - GPIO - SRAM - FPGA update +- ESC (EtherCAT@ Slave) +- IRQ ### Supported devices - [CX50xx](https://www.beckhoff.com/CX5000/) - [CX51xx](https://www.beckhoff.com/CX5100/) - [CX20xx](https://www.beckhoff.com/CX2000/) +- [FC1121](https://www.beckhoff.com/en-en/products/ipc/pcs/accessories/fc1121.html) ### How to build and install the kernel modules: diff --git a/esc.c b/esc.c new file mode 100644 index 0000000..1cdaf06 --- /dev/null +++ b/esc.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +/** + ESC Driver for Beckhoff CCAT FPGA ESCs + Copyright (C) 2024 DLR e.V. + Author: Robert Burger +*/ + +// vi: set noexpandtab: + +#include "module.h" +#include "sram.h" +#include +#include +#include +#include + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR("Robert Burger "); +MODULE_LICENSE("GPL and additional rights"); +MODULE_VERSION(DRV_VERSION); + +#define CCAT_ESC_DEVICES_MAX 4 + +static int ccat_esc_mmap(struct file *f, struct vm_area_struct *vma) +{ + struct cdev_buffer *const buffer = f->private_data; + struct pci_dev *pdev = (struct pci_dev *)(buffer->ccdev->func->ccat->pdev); + + vma->vm_pgoff = (pci_resource_start(pdev, 0) + buffer->ccdev->func->info.addr) >> PAGE_SHIFT; + vma->vm_flags |= VM_LOCKED | VM_DONTCOPY | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return remap_pfn_range( + vma, + vma->vm_start, + vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static struct ccat_cdev dev_table[CCAT_ESC_DEVICES_MAX]; +static struct ccat_class cdev_class = { + .instances = {0}, + .count = CCAT_ESC_DEVICES_MAX, + .devices = dev_table, + .name = "ccat_esc", + .fops = { + .owner = THIS_MODULE, + .llseek = ccat_cdev_llseek, + .open = ccat_cdev_open, + .release = ccat_cdev_release, + .read = ccat_sram_read, + .write = ccat_sram_write, + .mmap = ccat_esc_mmap, + }, +}; + +static int ccat_esc_probe(struct platform_device *pdev) +{ + struct ccat_function *const func = pdev->dev.platform_data; + + pr_info("%s: 0x%04x rev: 0x%04x, addr: 0x%X, size: 0x%X\n", __FUNCTION__, + func->info.type, func->info.rev, func->info.addr, func->info.size); + + return ccat_cdev_probe(func, &cdev_class, func->info.size, NULL); +} + +static struct platform_driver esc_driver = { + .driver = {.name = "ccat_esc"}, + .probe = ccat_esc_probe, + .remove = ccat_cdev_remove, +}; + +module_platform_driver(esc_driver); + diff --git a/etc/modprobe.d/ccat.conf b/etc/modprobe.d/ccat.conf new file mode 100644 index 0000000..2ca1eac --- /dev/null +++ b/etc/modprobe.d/ccat.conf @@ -0,0 +1 @@ +softdep ccat post: ccat_esc ccat_gpio ccat_irq ccat_netdev ccat_sram ccat_systemtime ccat_update diff --git a/etc/udev/rules.d/991-ccat.rules b/etc/udev/rules.d/991-ccat.rules new file mode 100644 index 0000000..2ea0968 --- /dev/null +++ b/etc/udev/rules.d/991-ccat.rules @@ -0,0 +1,4 @@ +# udev rules for CCAT devices: +SUBSYSTEM=="ccat_esc", KERNEL=="ccat_esc?", MODE="0666" +SUBSYSTEM=="ccat_irq", KERNEL=="ccat_irq?", MODE="0666" +SUBSYSTEM=="ccat_update", KERNEL=="ccat_update?", MODE="0666" diff --git a/irq.c b/irq.c new file mode 100644 index 0000000..8e22780 --- /dev/null +++ b/irq.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: MIT +/** + IRQ Driver for Beckhoff CCAT FPGA irq + Copyright (C) 2024 DLR e.V. + Author: Robert Burger +*/ + +// vi: set noexpandtab: + +#include "module.h" +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR("Robert Burger "); +MODULE_LICENSE("GPL and additional rights"); +MODULE_VERSION(DRV_VERSION); + +struct ccat_irq { + char name[16]; + int irq_num; + wait_queue_head_t ir_queue; // Interrupt wait queue. +}; + +#define CCAT_IRQ_FUNCTION_IRQ__SLOT_N(n) ((uint16_t)1u << n) +#define CCAT_IRQ_FUNCTION_IRQ__SLOT (CCAT_IRQ_FUNCTION_IRQ__SLOT_N(1)) + +#define CCAT_IRQ_FUNCTION_IRQ__STATUS_REG ((uint32_t)0x0u) // 2 byte / 1 word +#define CCAT_IRQ_FUNCTION_IRQ__CONTROL_REG ((uint32_t)0x8u) // 2 byte / 1 word + +#define CCAT_IRQ_GLOBAL_IRQ_STATUS_REG ((uint32_t)0x40u) // 1 byte +#define CCAT_IRQ_GLOBAL_IRQ_ENABLE_REG ((uint32_t)0x50u) // 1 byte + +#define CCAT_IRQ_GLOBAL_IRQ_ENABLE ((uint32_t)0x01u) // It seems to be there is a mistake in the documentation of + // the FC1121 card. In chapter 3.2.1 Interrupt it is said that + // the global interrupt enable/status is bit 7 but instead it + // is bit 0 !!! + +#define CCAT_IRQ_DEVICES_MAX 4 + +static uint16_t ccat_irq_get_slot_irq_stat(struct ccat_function *func) +{ + return ioread16(func->ccat->bar_0 + func->info.addr + CCAT_IRQ_FUNCTION_IRQ__STATUS_REG); +} + +static void ccat_irq_set_slot_irq_ctrl(struct ccat_function *func, uint16_t ctrl) +{ + iowrite16(ctrl, func->ccat->bar_0 + func->info.addr + CCAT_IRQ_FUNCTION_IRQ__CONTROL_REG); +} + +static void ccat_irq_set_slot_irq_stat(struct ccat_function *func, uint16_t ctrl) +{ + iowrite16(ctrl, func->ccat->bar_0 + func->info.addr + CCAT_IRQ_FUNCTION_IRQ__STATUS_REG); +} + +static uint8_t ccat_irq_get_global_irq_stat(struct ccat_function *func) +{ + return ioread8(func->ccat->bar_2 + CCAT_IRQ_GLOBAL_IRQ_STATUS_REG); +} + +static void ccat_irq_set_global_irq_ctrl(struct ccat_function *func, uint8_t ctrl) +{ + iowrite8(ctrl, func->ccat->bar_2 + CCAT_IRQ_GLOBAL_IRQ_ENABLE_REG); +} + +int ccat_irq_use_msi = 0; +module_param(ccat_irq_use_msi, int, 0); +MODULE_PARM_DESC(ccat_irq_use_msi, "Use MSI interrupts instead of legacy PCI"); + +static irqreturn_t ccat_irq_int_handler(int int_no, void *arg) { + struct cdev_buffer *buffer = (struct cdev_buffer *)arg; + struct ccat_irq *irq = (struct ccat_irq *)(buffer->ccdev->user); + irqreturn_t ret = IRQ_NONE; + uint32_t global_state = ccat_irq_get_global_irq_stat(buffer->ccdev->func); + + if (ccat_irq_use_msi != 0) { + // no need to check here, it's 100% sure it is ours + + // disable here until interrupt source is processed. + // will be re-enable in poll + ccat_irq_set_slot_irq_ctrl(buffer->ccdev->func, 0); + wake_up(&irq->ir_queue); + + ret = IRQ_HANDLED; + } else { + if (global_state & CCAT_IRQ_GLOBAL_IRQ_ENABLE) { + if (ccat_irq_get_slot_irq_stat(buffer->ccdev->func) & CCAT_IRQ_FUNCTION_IRQ__SLOT) { + // disable here until interrupt source is processed. + // will be re-enable in poll + ccat_irq_set_slot_irq_ctrl(buffer->ccdev->func, 0); + wake_up(&irq->ir_queue); + + // clear interrupt + ccat_irq_set_slot_irq_stat(buffer->ccdev->func, 0); + + ret = IRQ_HANDLED; + } + } + } + + return ret; +} + +static int ccat_irq_open(struct inode *const i, struct file *const f) +{ + struct ccat_cdev *ccdev = + container_of(i->i_cdev, struct ccat_cdev, cdev); + struct ccat_irq *irq = (struct ccat_irq *)(ccdev->user); + struct cdev_buffer *buf; + + if (!atomic_dec_and_test(&ccdev->in_use)) { + atomic_inc(&ccdev->in_use); + return -EBUSY; + } + + buf = kzalloc(sizeof(*buf) + ccdev->iosize, GFP_KERNEL); + if (!buf) { + atomic_inc(&ccdev->in_use); + return -ENOMEM; + } + + buf->ccdev = ccdev; + f->private_data = buf; + + if (ccat_irq_use_msi == 1) { + int num_vecs = pci_alloc_irq_vectors(ccdev->func->ccat->pdev, 1, 1, PCI_IRQ_ALL_TYPES); + if (num_vecs < 0 ) { + pr_err("Allocating IRQ vectors failed\n"); + } else { + int r, irq_success = 1; + pr_info("Got %d IRQ vectors\n", num_vecs); + + for (r = 0; r < num_vecs; ++r) { + irq->irq_num = pci_irq_vector(ccdev->func->ccat->pdev, r); + pr_info("Interrupt %d has been reserved, using irq name %s\n", irq->irq_num, &irq->name[0]); + + if (request_irq(irq->irq_num, ccat_irq_int_handler, IRQF_NO_THREAD | IRQF_NOBALANCING, &irq->name[0], buf)) { + pr_err("Interrupt %d reqeust failed!\n", irq->irq_num); + irq_success = 0; + } + } + + if (irq_success == 0) { + pci_disable_device(ccdev->func->ccat->pdev); + + return -EBUSY; + } else { + // disable all Interrupt slots + ccat_irq_set_slot_irq_ctrl(ccdev->func, 0); + ccat_irq_set_global_irq_ctrl(ccdev->func, CCAT_IRQ_GLOBAL_IRQ_ENABLE); + } + } + } else { + irq->irq_num = ((struct pci_dev *)(ccdev->func->ccat->pdev))->irq; + + pr_info("Interrupt %d has been reserved, using irq name %s\n", irq->irq_num, &irq->name[0]); + + if (request_irq(irq->irq_num, ccat_irq_int_handler, IRQF_SHARED | IRQF_NOBALANCING, &irq->name[0], buf)) { + pci_disable_device(ccdev->func->ccat->pdev); + + pr_err("Interrupt %d isn't free\n", irq->irq_num); + return -EBUSY; + } else { + // disable all Interrupt slots + ccat_irq_set_slot_irq_ctrl(ccdev->func, 0); + ccat_irq_set_global_irq_ctrl(ccdev->func, CCAT_IRQ_GLOBAL_IRQ_ENABLE); + } + } + + return 0; +} + +static int ccat_irq_release(struct inode *const i, struct file *const f) +{ + struct cdev_buffer *const buf = f->private_data; + struct ccat_cdev *const ccdev = buf->ccdev; + struct ccat_irq *irq = (struct ccat_irq *)(ccdev->user); + + ccat_irq_set_global_irq_ctrl(ccdev->func, 0); + + if (ccat_irq_use_msi == 1) { + // disable Interrupt (see FC1121 Application Notes 3.2.1) + free_irq(irq->irq_num, buf); + pci_free_irq_vectors(ccdev->func->ccat->pdev); + } else { + free_irq(irq->irq_num, buf); + } + + kfree(f->private_data); + atomic_inc(&ccdev->in_use); + return 0; +} + +static unsigned int ccat_irq_poll(struct file *f, struct poll_table_struct *poll_table) +{ + struct cdev_buffer *buffer = f->private_data; + struct ccat_irq *irq = (struct ccat_irq *)(buffer->ccdev->user); + + // status of slot 1 (see FC1121 Application Notes 3.2.1) + if (ccat_irq_get_slot_irq_stat(buffer->ccdev->func) & CCAT_IRQ_FUNCTION_IRQ__SLOT) { + return DEFAULT_POLLMASK; + } + + ccat_irq_set_slot_irq_ctrl(buffer->ccdev->func, CCAT_IRQ_FUNCTION_IRQ__SLOT); + poll_wait(f, &irq->ir_queue, poll_table); + return 0; +} + +static struct ccat_cdev dev_table[CCAT_IRQ_DEVICES_MAX]; +static struct ccat_class cdev_class = { + .instances = {0}, + .count = CCAT_IRQ_DEVICES_MAX, + .devices = dev_table, + .name = "ccat_irq", + .fops = { + .owner = THIS_MODULE, + .open = ccat_irq_open, + .release = ccat_irq_release, + .poll = ccat_irq_poll, + }, +}; + +static int ccat_irq_probe(struct platform_device *pdev) +{ + struct ccat_function *const func = pdev->dev.platform_data; + struct ccat_irq *const irq = kzalloc(sizeof(*irq), GFP_KERNEL); + int ret = 0; + + if (!irq) + return -ENOMEM; + + // init wait queue + init_waitqueue_head(&irq->ir_queue); + + pr_info("%s: 0x%04x rev: 0x%04x, addr: 0x%X, size: 0x%X\n", __FUNCTION__, + func->info.type, func->info.rev, func->info.addr, func->info.size); + + ret = ccat_cdev_probe(func, &cdev_class, func->info.size, irq); + + if (ret == 0) { + struct ccat_cdev *ccdev = (struct ccat_cdev *)func->private_data; + snprintf(&irq->name[0], 16, "esc%d", MINOR(ccdev->dev)); + } + + return ret; +} + +static struct platform_driver irq_driver = { + .driver = {.name = "ccat_irq"}, + .probe = ccat_irq_probe, + .remove = ccat_cdev_remove, // TODO release ir_queue +}; + +module_platform_driver(irq_driver); + + diff --git a/module.c b/module.c index c218ab1..e912436 100644 --- a/module.c +++ b/module.c @@ -5,6 +5,8 @@ Author: Patrick Bruenn */ +// vi: noexpandtab: + #include #include #include @@ -18,6 +20,14 @@ MODULE_LICENSE("GPL and additional rights"); MODULE_VERSION(DRV_VERSION); static struct ccat_cell ccat_cells[] = { + { + .type = CCATINFO_INFO, + .cell = {.name = "ccat_info"}, + }, + { + .type = CCATINFO_ETHERCAT_SLAVE, + .cell = {.name = "ccat_esc"}, + }, { .type = CCATINFO_ETHERCAT_NODMA, .cell = {.name = "ccat_eth_eim"}, @@ -42,6 +52,14 @@ static struct ccat_cell ccat_cells[] = { .type = CCATINFO_SYSTEMTIME, .cell = {.name = "ccat_systemtime"}, }, + { + .type = CCATINFO_IRQ, + .cell = {.name = "ccat_irq"}, + }, + { + .type = CCATINFO_EEPROM, + .cell = {.name = "ccat_eeprom"}, + }, }; static int __init ccat_class_init(struct ccat_class *base) @@ -152,7 +170,7 @@ int ccat_cdev_open(struct inode *const i, struct file *const f) EXPORT_SYMBOL(ccat_cdev_open); int ccat_cdev_probe(struct ccat_function *func, struct ccat_class *cdev_class, - size_t iosize) + size_t iosize, void *user) { struct ccat_cdev *const ccdev = alloc_ccat_cdev(cdev_class); if (!ccdev) { @@ -161,6 +179,8 @@ int ccat_cdev_probe(struct ccat_function *func, struct ccat_class *cdev_class, ccdev->ioaddr = func->ccat->bar_0 + func->info.addr; ccdev->iosize = iosize; + ccdev->func = func; + ccdev->user = user; atomic_set(&ccdev->in_use, 1); if (ccat_cdev_init diff --git a/module.h b/module.h index 9aee94b..8101c88 100644 --- a/module.h +++ b/module.h @@ -17,6 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +// vi: noexpandtab: #ifndef _CCAT_H_ #define _CCAT_H_ @@ -40,10 +42,14 @@ */ enum ccat_info_t { CCATINFO_NOTUSED = 0, + CCATINFO_INFO = 0x1, + CCATINFO_ETHERCAT_SLAVE = 0x2, CCATINFO_ETHERCAT_NODMA = 0x3, CCATINFO_GPIO = 0xd, CCATINFO_EPCS_PROM = 0xf, CCATINFO_SYSTEMTIME = 0x10, + CCATINFO_IRQ = 0x11, + CCATINFO_EEPROM = 0x12, CCATINFO_ETHERCAT_MASTER_DMA = 0x14, CCATINFO_SRAM = 0x16, }; @@ -65,6 +71,8 @@ struct ccat_cdev { dev_t dev; struct cdev cdev; struct ccat_class *class; + struct ccat_function *func; + void *user; }; /** @@ -119,6 +127,10 @@ struct ccat_info_block { u8 sram_size; u16 reserved; }; + struct { + u16 revision; + u16 parameter; + }; }; u32 addr; u32 size; @@ -142,6 +154,6 @@ struct ccat_class { extern int ccat_cdev_remove(struct platform_device *pdev); extern int ccat_cdev_probe(struct ccat_function *func, - struct ccat_class *cdev_class, size_t iosize); + struct ccat_class *cdev_class, size_t iosize, void *user); #endif /* #ifndef _CCAT_H_ */ diff --git a/sram.c b/sram.c index 6934641..37ebd3b 100644 --- a/sram.c +++ b/sram.c @@ -5,7 +5,10 @@ Author: Patrick Bruenn */ +// vi: set noexpandtab: + #include "module.h" +#include "sram.h" #include #include #include @@ -29,7 +32,7 @@ static ssize_t __sram_read(struct cdev_buffer *buffer, char __user * buf, return len; } -static ssize_t ccat_sram_read(struct file *const f, char __user * buf, +ssize_t ccat_sram_read(struct file *const f, char __user * buf, size_t len, loff_t * off) { struct cdev_buffer *buffer = f->private_data; @@ -42,9 +45,11 @@ static ssize_t ccat_sram_read(struct file *const f, char __user * buf, len = min(len, (size_t) (iosize - *off)); return __sram_read(buffer, buf, len, off); -} +} -static ssize_t ccat_sram_write(struct file *const f, const char __user * buf, +EXPORT_SYMBOL(ccat_sram_read); + +ssize_t ccat_sram_write(struct file *const f, const char __user * buf, size_t len, loff_t * off) { struct cdev_buffer *const buffer = f->private_data; @@ -63,6 +68,8 @@ static ssize_t ccat_sram_write(struct file *const f, const char __user * buf, return len; } +EXPORT_SYMBOL(ccat_sram_write); + static struct ccat_cdev dev_table[CCAT_SRAM_DEVICES_MAX]; static struct ccat_class cdev_class = { .instances = {0}, @@ -91,7 +98,7 @@ static int ccat_sram_probe(struct platform_device *pdev) if (type == NO_SRAM_CONNECTED) { return -ENODEV; } - return ccat_cdev_probe(func, &cdev_class, iosize); + return ccat_cdev_probe(func, &cdev_class, iosize, NULL); } static struct platform_driver sram_driver = { diff --git a/sram.h b/sram.h new file mode 100644 index 0000000..71b3bc8 --- /dev/null +++ b/sram.h @@ -0,0 +1,35 @@ +/** + Network Driver for Beckhoff CCAT communication controller + Copyright (C) 2014 Beckhoff Automation GmbH + Author: Patrick Bruenn + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +// vi: set noexpandtab: + +#ifndef _CCAT_SRAM_H_ +#define _CCAT_SRAM_H_ + +#include + +extern ssize_t ccat_sram_read(struct file *const f, char __user * buf, + size_t len, loff_t * off); + +extern ssize_t ccat_sram_write(struct file *const f, const char __user * buf, + size_t len, loff_t * off); + +#endif /* #ifndef _CCAT_SRAM_H_ */ + diff --git a/update.c b/update.c index 4ddcf66..0f3c12a 100644 --- a/update.c +++ b/update.c @@ -333,7 +333,7 @@ static int ccat_update_probe(struct platform_device *pdev) pr_warn("CCAT Update rev. %d not supported\n", func->info.rev); return -ENODEV; } - return ccat_cdev_probe(func, &cdev_class, CCAT_FLASH_SIZE); + return ccat_cdev_probe(func, &cdev_class, CCAT_FLASH_SIZE, NULL); } static struct platform_driver update_driver = {