Commit 947d74fe authored by Jens Korinth's avatar Jens Korinth
Browse files

WIP: PCIe device support basics

parent 60308415
......@@ -26,7 +26,10 @@ tlkm-objs := \
zynq/zynq_ioctl.o \
zynq/zynq_dmamgmt.o \
zynq/zynq_irq.o \
zynq/zynq_mmap.o
zynq/zynq_mmap.o \
pcie/pcie.o \
pcie/pcie_device.o \
pcie/pcie_irq.o
CPPFLAGS+=-I$(TAPASCO_HOME)/tlkm -I$(TAPASCO_HOME)/tlkm/device -I$(TAPASCO_HOME)/tlkm/tlkm -I$(TAPASCO_HOME)/tlkm/user -I$(TAPASCO_HOME)/platform/include
......
......@@ -49,7 +49,13 @@ void tlkm_perfc_ ## name ## _add(dev_id_t dev_id, int const v) \
int tlkm_perfc_ ## name ## _get(dev_id_t dev_id) \
{ \
return atomic_read(&tlkm_perfc.pc_ ## name[dev_id]); \
} \
\
void tlkm_perfc_ ## name ## _set(dev_id_t dev_id, int const v) \
{ \
atomic_set(&tlkm_perfc.pc_ ## name[dev_id], v); \
}
TLKM_PERFC_COUNTERS
#undef _PC
......
......@@ -41,7 +41,9 @@
_PC(total_usr2dev_transfers) \
_PC(total_dev2usr_transfers) \
_PC(total_ctl_writes) \
_PC(total_ctl_reads)
_PC(total_ctl_reads) \
_PC(link_width) \
_PC(link_speed)
#ifndef NPERFC
#include <linux/types.h>
......@@ -49,7 +51,8 @@
#define _PC(name) \
void tlkm_perfc_ ## name ## _inc(dev_id_t dev_id); \
void tlkm_perfc_ ## name ## _add(dev_id_t dev_id, int const v); \
int tlkm_perfc_ ## name ## _get(dev_id_t dev_id);
int tlkm_perfc_ ## name ## _get(dev_id_t dev_id); \
void tlkm_perfc_ ## name ## _set(dev_id_t dev_id, int const v);
TLKM_PERFC_COUNTERS
#undef _PC
......@@ -58,6 +61,7 @@
inline static void tlkm_perfc_ ## name ## _inc(dev_id_t dev_id) {} \
inline static void tlkm_perfc_ ## name ## _add(dev_id_t dev_id, int const v) {} \
inline static int tlkm_perfc_ ## name ## _get(dev_id_t dev_id) { return 0; } \
inline static void tlkm_perfc_ ## name ## _set(dev_id_t dev_id, int const v) {}
TLKM_PERFC_COUNTERS
#undef _PC
......
#include <linux/pci.h>
#include <linux/module.h>
#include "tlkm_logging.h"
#include "pcie/pcie.h"
#include "pcie/pcie_device.h"
#define XILINX_VENDOR_ID 0x10EE
#define XILINX_DEVICE_ID 0x7038
static const struct pci_device_id tlkm_pcie_id[] = {
{ PCI_DEVICE( XILINX_VENDOR_ID , XILINX_DEVICE_ID ) },
{},
};
// struct representation of functions above similiar to fops
static struct pci_driver tlkm_pcie_driver = {
.name = TLKM_PCI_NAME,
.id_table = tlkm_pcie_id,
.probe = tlkm_pcie_probe,
.remove = tlkm_pcie_remove,
};
int pcie_init()
{
int err = 0;
LOG(TLKM_LF_PCIE, "registering TLKM PCIe driver ...");
if ((err = pci_register_driver(&tlkm_pcie_driver))) {
LOG(TLKM_LF_PCIE, "no PCIe TaPaSCo devices found");
}
return 0;
}
void pcie_deinit()
{
pci_unregister_driver(&tlkm_pcie_driver);
LOG(TLKM_LF_PCIE, "deregistered TLKM PCIe driver");
}
MODULE_DEVICE_TABLE(pci, tlkm_pcie_id);
#ifndef PCIE_H__
#define PCIE_H__
#define TLKM_PCI_NAME "tlkm"
int pcie_init(void);
void pcie_deinit(void);
#endif /* PCIE_H__ */
#include <linux/pci.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/gfp.h>
#include <linux/list.h>
#include "platform_global.h"
#include "pcie.h"
#include "pcie_device.h"
#include "pcie_irq.h"
#include "tlkm_logging.h"
#include "tlkm_device.h"
#include "tlkm_bus.h"
static size_t num_devices = 0;
ssize_t pcie_enumerate(void) { return num_devices; }
static struct tlkm_device devices[PLATFORM_MAX_DEVS];
static int tlkm_pcie_init(struct tlkm_device_inst *d) { return 0; }
static void tlkm_pcie_exit(struct tlkm_device_inst *d) {}
static struct tlkm_device tlkm_pcie_dev = {
.device = LIST_HEAD_INIT(tlkm_pcie_dev.device),
.name = TLKM_PCI_NAME,
.init = tlkm_pcie_init,
.exit = tlkm_pcie_exit,
};
/**
* @brief Enables pcie-device and claims/remaps neccessary bar resources
* @param pdev Pointer to pci-device, which should be allocated
* @return Returns error code or zero if success
* */
static int claim_device(struct pci_dev *pdev)
{
int err = 0;
struct tlkm_pcie_device *pci_data;
/* wake up the pci device */
err = pci_enable_device(pdev);
if (err) {
WRN("failed to enable PCIe-device: %d", err);
goto error_pci_en;
}
/* set busmaster bit in config register */
pci_set_master(pdev);
/* setup BAR memory regions */
err = pci_request_regions(pdev, TLKM_PCI_NAME);
if (err) {
WRN("failed to setup bar regions for pci device %d", err);
goto error_pci_req;
}
pci_data = (struct tlkm_pcie_device *)dev_get_drvdata(&pdev->dev);
if (! pci_data) {
pci_data = (struct tlkm_pcie_device *)kzalloc(sizeof(*pci_data), GFP_KERNEL);
if (! pci_data) {
ERR("could not allocate private PCI data struct");
goto error_pci_req;
}
dev_set_drvdata(&pdev->dev, pci_data);
}
/* read out pci bar 0 settings */
pci_data->pdev = pdev;
pci_data->phy_addr_bar0 = pci_resource_start(pdev, 0);
pci_data->phy_len_bar0 = pci_resource_len(pdev, 0);
pci_data->phy_flags_bar0 = pci_resource_flags(pdev, 0);
LOG(TLKM_LF_PCIE, "PCI bar 0: address: %llx length: %llx",
pci_data->phy_addr_bar0, pci_data->phy_len_bar0);
/* map physical address to kernel space */
pci_data->kvirt_addr_bar0 = ioremap(pci_data->phy_addr_bar0, pci_data->phy_len_bar0);
if (IS_ERR(pci_data->kvirt_addr_bar0)) {
ERR("could not remap bar 0 address to kernel space");
goto error_pci_remap;
}
LOG(TLKM_LF_PCIE, "remapped Bar 0 Address is: %llx", (u64)pci_data->kvirt_addr_bar2);
tlkm_bus_add_device(&tlkm_pcie_dev);
pci_data->dev_id = tlkm_pcie_dev.dev_id;
devices[pci_data->dev_id] = tlkm_pcie_dev;
tlkm_perfc_link_speed_set(pci_data->dev_id, pci_data->link_speed);
tlkm_perfc_link_width_set(pci_data->dev_id, pci_data->link_width);
num_devices++;
return 0;
error_pci_remap:
pci_release_regions(pdev);
kfree(pci_data);
error_pci_req:
pci_disable_device(pdev);
error_pci_en:
return -ENODEV;
}
static void release_device(struct pci_dev *pdev)
{
struct tlkm_pcie_device *dev = (struct tlkm_pcie_device *)dev_get_drvdata(&pdev->dev);
BUG_ON(! dev);
iounmap(dev->kvirt_addr_bar0);
kfree(dev);
pci_release_regions(pdev);
pci_disable_device(pdev);
}
/**
* @brief Configures pcie-device and bit_mask settings
* @param pdev Pointer to pci-device, which should be allocated
* @return Returns error code or zero if success
* */
static int configure_device(struct pci_dev *pdev)
{
LOG(TLKM_LF_PCIE, "MPS: %d, Maximum Read Requests %d", pcie_get_mps(pdev), pcie_get_readrq(pdev));
if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
LOG(TLKM_LF_PCIE, "dma_set_mask: using 64 bit DMA addresses");
dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
} else if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) {
LOG(TLKM_LF_PCIE, "dma_set_mask: using 32 bit DMA addresses");
dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
} else {
ERR("no suitable DMA bitmask available");
return -ENODEV;
}
return 0;
}
/**
* @brief Initializes msi-capable structure and binds to irq_functions
* @param pci_dev device, which should be allocated
* @return Returns error code or zero if success
* */
static int claim_msi(struct pci_dev *pdev)
{
int err = 0, i;
struct tlkm_pcie_device *pci_data = (struct tlkm_pcie_device *)dev_get_drvdata(&pdev->dev);
BUG_ON(! pci_data);
for (i = 0; i < REQUIRED_INTERRUPTS; i++) {
pci_data->irq_mapping[i] = -1;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
pci_data->msix_entries[i].entry = i;
#endif
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
err = pci_enable_msix_range(pdev,
pci_data->msix_entries,
REQUIRED_INTERRUPTS,
REQUIRED_INTERRUPTS);
#else
/* set up MSI interrupt vector to max size */
err = pci_alloc_irq_vectors(pdev,
REQUIRED_INTERRUPTS,
REQUIRED_INTERRUPTS,
PCI_IRQ_MSIX);
#endif
if (err <= 0) {
ERR("cannot set MSI vector (%d)", err);
return -ENOSPC;
} else {
WRN("got %d MSI vectors", err);
}
if ((err = pcie_irqs_init(pdev))) {
ERR("failed to register interrupts: %d", err);
return -ENOSPC;
}
return 0;
}
static void report_link_status(struct pci_dev *pdev)
{
struct tlkm_pcie_device *dev = (struct tlkm_pcie_device *)dev_get_drvdata(&pdev->dev);
u16 ctrl_reg = 0;
double gts = 0.0;
BUG_ON(! dev);
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &ctrl_reg);
dev->link_width = (ctrl_reg & PCI_EXP_LNKSTA_NLW) >> PCI_EXP_LNKSTA_NLW_SHIFT;
dev->link_speed = ctrl_reg & PCI_EXP_LNKSTA_CLS;
switch (dev->link_speed) {
case PCI_EXP_LNKSTA_CLS_8_0GB: gts = 8.0; break;
case PCI_EXP_LNKSTA_CLS_5_0GB: gts = 5.0; break;
case PCI_EXP_LNKSTA_CLS_2_5GB: gts = 2.5; break;
default: gts = 0.0; break;
}
LOG(TLKM_LF_PCIE, "PCIe link width: x%d", dev->link_width);
LOG(TLKM_LF_PCIE, "PCIe link speed: %1.1f GT/s", gts);
}
int tlkm_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
LOG(TLKM_LF_PCIE, "init pcie-dev for bus mastering");
if (claim_device(pdev)) {
goto error_no_device;
}
if (configure_device(pdev)) {
goto error_cannot_configure;
}
if (claim_msi(pdev)) {
goto error_cannot_configure;
}
report_link_status(pdev);
if (0/*char_dma_register()*/) {
LOG(TLKM_LF_PCIE, "could not register dma char device(s)");
goto error_cannot_configure;
}
if (0/*char_user_register()*/) {
LOG(TLKM_LF_PCIE, "could not register user char device(s)");
goto error_user_register;
}
return 0;
//char_user_unregister();
error_user_register:
//char_dma_unregister();
error_cannot_configure:
release_device(pdev);
error_no_device:
return -ENOSPC;
}
void tlkm_pcie_remove(struct pci_dev *pdev)
{
LOG(TLKM_LF_PCIE, "unload pci-device");
//char_dma_unregister();
//char_user_unregister();
pcie_irqs_deinit(pdev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
pci_disable_msix(pdev);
#else
pci_free_irq_vectors(pdev);
#endif
release_device(pdev);
}
//
// Copyright (C) 2014-2018 Jens Korinth, TU Darmstadt
//
// This file is part of Tapasco (TaPaSCo).
//
// Tapasco is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tapasco 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Tapasco. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef PCIE_DEVICE_H__
#define PCIE_DEVICE_H__
#include <linux/workqueue.h>
#include "tlkm_types.h"
#include "platform_global.h"
#define TLKM_PLATFORM_INTERRUPTS 4
#define TLKM_SLOT_INTERRUPTS PLATFORM_NUM_SLOTS
#define REQUIRED_INTERRUPTS \
(TLKM_PLATFORM_INTERRUPTS + TLKM_SLOT_INTERRUPTS)
int tlkm_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id);
void tlkm_pcie_remove(struct pci_dev *pdev);
/* struct to hold data related to the pcie device */
struct tlkm_pcie_device {
dev_id_t dev_id;
struct pci_dev *pdev;
u64 phy_addr_bar0;
u64 phy_len_bar0;
u64 phy_flags_bar0;
u64 phy_addr_bar2;
u64 phy_len_bar2;
u64 phy_flags_bar2;
int irq_mapping[REQUIRED_INTERRUPTS];
void *kvirt_addr_bar0;
void *kvirt_addr_bar2;
int link_width;
int link_speed;
struct work_struct irq_work[TLKM_SLOT_INTERRUPTS];
struct tlkm_control *ctrl;
};
ssize_t pcie_enumerate(void);
#endif // PCIE_DEVICE_H__
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/version.h>
#include <linux/atomic.h>
#include "tlkm_logging.h"
#include "tlkm_control.h"
#include "pcie/pcie.h"
#include "pcie/pcie_irq.h"
#include "pcie/pcie_device.h"
#define _INTR(nr) \
void tlkm_pcie_slot_irq_work_ ## nr(struct work_struct *work) \
{ \
struct tlkm_pcie_device *dev = (struct tlkm_pcie_device *)atomic_long_read(&work->data); \
BUG_ON(! dev->ctrl); \
tlkm_control_signal_slot_interrupt(dev->ctrl, nr); \
} \
\
irqreturn_t tlkm_pcie_slot_irq_ ## nr(int irq, void *dev_id) \
{ \
struct pci_dev *pdev = (struct pci_dev *)dev_id; \
struct tlkm_pcie_device *dev = (struct tlkm_pcie_device *) dev_get_drvdata(&pdev->dev); \
BUG_ON(! dev); \
schedule_work(&dev->irq_work[nr]); \
return IRQ_HANDLED; \
}
TLKM_PCIE_SLOT_INTERRUPTS
#undef _INTR
int pcie_irqs_init(struct pci_dev *pdev)
{
#define _INTR(nr) + 1
size_t const n = 0 TLKM_PCIE_SLOT_INTERRUPTS;
#undef _INTR
struct tlkm_pcie_device *dev = (struct tlkm_pcie_device *)dev_get_drvdata(&pdev->dev);
int ret = 0, err[n];
BUG_ON(! dev);
LOG(TLKM_LF_PCIE, "registering %zu interrupts ...", n);
#define _INTR(nr) \
if ((err[nr] = request_irq(pci_irq_vector(pdev, nr), \
tlkm_pcie_slot_irq_ ## nr, \
IRQF_EARLY_RESUME, \
TLKM_PCI_NAME, \
pdev))) { \
ERR("could not request interrupt %d: %d", nr, err[nr]); \
goto irq_error; \
} else { \
dev->irq_mapping[nr] = pci_irq_vector(pdev, nr); \
LOG(TLKM_LF_PCIE, "created mapping from interrupt %d -> %d", nr, dev->irq_mapping[nr]); \
LOG(TLKM_LF_PCIE, "interrupt line %d/%d assigned with return value %d", \
nr, pci_irq_vector(pdev, nr), err[nr]); \
INIT_WORK(&dev->irq_work[nr], tlkm_pcie_slot_irq_work_ ## nr); \
atomic_long_set(&dev->irq_work[nr].data, (long)dev); \
}
TLKM_PCIE_SLOT_INTERRUPTS
#undef _INTR
return 0;
irq_error:
#define _INTR(nr) \
if (! err[nr]) { \
free_irq(dev->irq_mapping[nr], pdev); \
dev->irq_mapping[nr] = -1; \
} else { \
ret = err[nr]; \
}
TLKM_PCIE_SLOT_INTERRUPTS
#undef _INTR
return ret;
}
void pcie_irqs_deinit(struct pci_dev *pdev)
{
struct tlkm_pcie_device *dev = (struct tlkm_pcie_device *)dev_get_drvdata(&pdev->dev);
BUG_ON(! dev);
#define _INTR(nr) \
if (dev->irq_mapping[nr] != -1) { \
LOG(TLKM_LF_PCIE, "freeing intterupt %d with mappping %d", nr, dev->irq_mapping[nr]); \
free_irq(dev->irq_mapping[nr], pdev); \
dev->irq_mapping[nr] = -1; \
}
TLKM_PCIE_SLOT_INTERRUPTS
#undef _INTR
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
pci_disable_msix(pdev);
#else
pci_free_irq_vectors(pdev);
#endif
}
#ifndef PCIE_IRQ_H__
#define PCIE_IRQ_H__
#ifdef _INTR
#undef _INTR
#endif
#define TLKM_PCIE_SLOT_INTERRUPTS \
_INTR(0) \
_INTR(1) \
_INTR(2) \
_INTR(3) \
_INTR(4) \
_INTR(5) \
_INTR(6) \
_INTR(7) \
_INTR(8) \
_INTR(9) \
_INTR(10) \
_INTR(11) \
_INTR(12) \
_INTR(13) \
_INTR(14) \
_INTR(15) \
_INTR(16) \
_INTR(17) \
_INTR(18) \
_INTR(19) \
_INTR(20) \
_INTR(21) \
_INTR(22) \
_INTR(23) \
_INTR(24) \
_INTR(25) \
_INTR(26) \
_INTR(27) \
_INTR(28) \
_INTR(29) \
_INTR(30) \
_INTR(31) \
_INTR(32) \
_INTR(33) \
_INTR(34) \
_INTR(35) \
_INTR(36) \
_INTR(37) \
_INTR(38) \
_INTR(39) \
_INTR(40) \
_INTR(41) \
_INTR(42) \
_INTR(43) \
_INTR(44) \
_INTR(45) \
_INTR(46) \
_INTR(47) \
_INTR(48) \
_INTR(49) \
_INTR(50) \
_INTR(51) \
_INTR(52) \
_INTR(53) \
_INTR(54) \
_INTR(55) \
_INTR(56) \
_INTR(57) \
_INTR(58) \
_INTR(59) \
_INTR(60) \
_INTR(61) \
_INTR(62) \
_INTR(63) \
_INTR(64) \
_INTR(65) \
_INTR(66) \
_INTR(67) \
_INTR(68) \
_INTR(69) \
_INTR(70) \
_INTR(71) \
_INTR(72) \
_INTR(73) \
_INTR(74) \
_INTR(75) \
_INTR(76) \
_INTR(77) \
_INTR(78) \
_INTR(79) \
_INTR(80) \
_INTR(81) \
_INTR(82) \
_INTR(83) \
_INTR(84) \
_INTR(85) \
_INTR(86) \
_INTR(87) \
_INTR(88) \
_INTR(89) \
_INTR(90) \
_INTR(91) \
_INTR(92) \
_INTR(93) \
_INTR(94) \
_INTR(95) \
_INTR(96) \
_INTR(97) \
_INTR(98) \
_INTR(99) \
_INTR(100) \
_INTR(101) \
_INTR(102) \
_INTR(103) \
_INTR(104) \
_INTR(105) \
_INTR(106) \
_INTR(107) \
_INTR(108) \
_INTR(109) \
_INTR(110) \
_INTR(111) \
_INTR(112) \
_INTR(113) \
_INTR(114) \
_INTR(115) \
_INTR(116) \
_INTR(117) \
_INTR(118) \
_INTR(119) \
_INTR(120) \
_INTR(121) \
_INTR(122) \
_INTR(123) \