把ZYNQ变成一个网口下载器,调试其他的板子。
项目地址
https://github.com/Xilinx/XilinxVirtualCable
移植指南
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/644579329/Xilinx+Virtual+Cable
编译驱动和app
bash
# 拉取代码
git clone https://github.com/Xilinx/XilinxVirtualCable.git
cd .\XilinxVirtualCable\jtag\zynqMP\src\driver\
wsl
ssdk
# 编译驱动
make modules KERNEL_SRC=/home/minglie/workspace/kernel-driver/linux-xlnx-xlnx_rebase_v5.4_2020.2
# 编译app
cd /mnt/e/ant_s9_fix_nand/XilinxVirtualCable/jtag/zynqMP/src/user
make -e MYCC="$CC"
加载驱动,启动xvcServer_ioctl
bash
root@ant:~# ls
xilinx_xvc_driver.ko xvcServer_ioctl
root@ant:~# insmod xilinx_xvc_driver.ko
xilinx_xvc_driver: loading out-of-tree module taints kernel.
xilinx_xvc_driver: Starting...
xilinx_xvc_driver: Created device xilinx_xvc_driver
root@ant:~# ./xvcServer_ioctl
INFO: XVC driver character file: /dev/xilinx_xvc_driver
INFO: debug_bridge base address: 0x43C10000
INFO: debug_bridge size: 0x10000
INFO: debug_bridge device tree compatibility string: xlnx,xvc
INFO: To connect to this xvcServer instance, use url: TCP:z8:2542
设备树
bash
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/media/xilinx-vip.h>
#include <dt-bindings/phy/phy.h>
/ {
model = "z7 Board ant";
compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000";
chosen {
bootargs = "console=ttyPS0,115200 earlycon=cdns,mmio,0xe0000000,115200n8 keep_bootcon earlyprintk root=/dev/mmcblk0p2 rw rootwait";
stdout-path = "serial0:115200n8";
};
leds {
compatible = "gpio-leds";
gpio-led1 {
label = "led2";
gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
gpio-led5 {
label = "ps_led0";
gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
gpio-led6 {
label = "ps_led1";
gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "timer";
};
};
};
&debug_bridge_0 {
compatible = "xlnx,xvc";
};
驱动源码
bash
minglie@DESKTOP-NTI9KM5:/mnt/e/ant_s9_fix_nand/XilinxVirtualCable/jtag/zynqMP/src/driver$ ll
total 32
drwxrwxrwx 1 minglie minglie 4096 Jul 23 23:32 ./
drwxrwxrwx 1 minglie minglie 4096 Jul 23 22:05 ../
-rwxrwxrwx 1 minglie minglie 535 Jul 23 22:05 Makefile*
-rwxrwxrwx 1 minglie minglie 3625 Jul 23 22:05 README.md*
-rwxrwxrwx 1 minglie minglie 5659 Jul 23 22:05 xvc_driver.c*
-rwxrwxrwx 1 minglie minglie 1422 Jul 23 22:05 xvc_driver.h*
-rwxrwxrwx 1 minglie minglie 8086 Jul 23 22:05 xvc_driver_base.c*
-rwxrwxrwx 1 minglie minglie 59 Jul 23 22:05 xvc_fragment.dts*
-rwxrwxrwx 1 minglie minglie 1409 Jul 23 22:05 xvc_ioctl.h*
-rwxrwxrwx 1 minglie minglie 27 Jul 23 22:05 xvc_user_config.h*
-rwxrwxrwx 1 minglie minglie 2947 Jul 23 22:05 xvc_user_config_example.h*
Makefile
c
DRIVER := xilinx_xvc_driver
export CROSS_COMPILE ?= aarch64-linux-gnu-
export ARCH ?= arm64
ccflags-y := -DLOG_PREFIX=\"$(DRIVER):\ \"
$(DRIVER)-y := \
xvc_driver_base.o \
xvc_driver.o
obj-m += $(DRIVER).o
SRC := $(shell pwd)
modules:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules
modules_install:
$(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
.PHONY: clean
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -f Module.markers Module.symvers modules.order
rm -rf .tmp_versions Modules.symvers
xvc_driver.h
c
/*
* Xilinx XVC Driver
* Copyright (C) 2019 Xilinx Corporation
*
* 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.
*/
#ifndef _XVC_DRIVER_H
#define _XVC_DRIVER_H
#include "xvc_ioctl.h"
#include "xvc_user_config.h"
#ifndef _XVC_USER_CONFIG_H
#define XVC_DRIVER_NAME "xilinx_xvc_driver"
#define DEBUG_BRIDGE_COMPAT_STRING "xlnx,xvc"
// debug bridge configuration
struct db_config {
const char* name;
unsigned long base_addr;
unsigned long size;
};
#endif
#ifndef LOG_PREFIX
#define LOG_PREFIX XVC_DRIVER_NAME ": "
#endif
long xil_xvc_ioctl(unsigned char* db_ptr, const char __user* arg);
long xil_xvc_readprops(const struct db_config* db_config, const char __user* arg);
#endif /* _XVC_DRIVER_H */
xvc_driver.c
c
/*
* Xilinx XVC Driver
* Copyright (C) 2019 Xilinx Corporation
*
* 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.
*/
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "xvc_driver.h"
#define LENGTH_REG_OFFSET (0)
#define TMS_REG_OFFSET (4)
#define TDI_REG_OFFSET (8)
#define TDO_REG_OFFSET (12)
#define CONTROL_REG_OFFSET (16)
static int xil_xvc_shift_bits(unsigned char* db_ptr, u32 tms_bits, u32 tdi_bits, u32 *tdo_bits) {
int status = 0;
u32 control_reg_data;
u32 write_reg_data;
int count = 100;
// Set tms bits
iowrite32(tms_bits, db_ptr + TMS_REG_OFFSET);
// Set tdi bits and shift data out
iowrite32(tdi_bits, db_ptr + TDI_REG_OFFSET);
// Read control register
control_reg_data = ioread32(db_ptr + CONTROL_REG_OFFSET);
// Enable shift operation in control register
write_reg_data = control_reg_data | 0x01;
// Write control register
iowrite32(write_reg_data, db_ptr + CONTROL_REG_OFFSET);
while (count) {
// Read control reg to check shift operation completion
control_reg_data = ioread32(db_ptr + CONTROL_REG_OFFSET);
if ((control_reg_data & 0x01) == 0) {
break;
}
count--;
}
if (count == 0) {
printk(KERN_ERR LOG_PREFIX "XVC transaction timed out (%0X)\n", control_reg_data);
return -ETIMEDOUT;
}
// Read tdo bits back out
*tdo_bits = ioread32(db_ptr + TDO_REG_OFFSET);
return status;
}
long xil_xvc_ioctl(unsigned char* db_ptr, const char __user *arg) {
struct xil_xvc_ioc xvc_obj;
u32 operation_code;
u32 num_bits;
int num_bytes;
char *tms_buf_temp = NULL;
char *tdi_buf_temp = NULL;
char *tdo_buf_temp = NULL;
int current_bit;
u32 bypass_status;
long status = 0;
if ((status = copy_from_user((void *)&xvc_obj, arg, sizeof(struct xil_xvc_ioc)))) {
goto cleanup;
}
operation_code = xvc_obj.opcode;
// Invalid operation type, no operation performed
if (operation_code != 0x01 && operation_code != 0x02) {
return 0;
}
num_bits = xvc_obj.length;
num_bytes = (num_bits + 7) / 8;
// Allocate and copy data into temporary buffers
tms_buf_temp = (char*) kmalloc(num_bytes, GFP_KERNEL);
if (tms_buf_temp == NULL) {
status = -ENOMEM;
goto cleanup;
}
if ((status = copy_from_user((void *)tms_buf_temp, xvc_obj.tms_buf, num_bytes))) {
goto cleanup;
}
tdi_buf_temp = (char*) kmalloc(num_bytes, GFP_KERNEL);
if (tdi_buf_temp == NULL) {
status = -ENOMEM;
goto cleanup;
}
if ((status = copy_from_user((void *)tdi_buf_temp, xvc_obj.tdi_buf, num_bytes))) {
goto cleanup;
}
// Allocate TDO buffer
tdo_buf_temp = (char*) kmalloc(num_bytes, GFP_KERNEL);
if (tdo_buf_temp == NULL) {
status = -ENOMEM;
goto cleanup;
}
if (operation_code == 0x2) {
bypass_status = 0x2;
} else {
bypass_status = 0x0;
}
iowrite32(bypass_status, db_ptr + CONTROL_REG_OFFSET);
// Set length register to 32 initially if more than one word-transaction is to be done
if (num_bits >= 32) {
iowrite32(0x20, db_ptr + LENGTH_REG_OFFSET);
}
current_bit = 0;
while (current_bit < num_bits) {
int shift_num_bytes;
int shift_num_bits = 32;
u32 tms_store = 0;
u32 tdi_store = 0;
u32 tdo_store = 0;
if (num_bits - current_bit < shift_num_bits) {
shift_num_bits = num_bits - current_bit;
// do LENGTH_REG_OFFSET here
// Set number of bits to shift out
iowrite32(shift_num_bits, db_ptr + LENGTH_REG_OFFSET);
}
// Copy only the remaining number of bytes out of user-space
shift_num_bytes = (shift_num_bits + 7) / 8;
memcpy(&tms_store, tms_buf_temp + (current_bit / 8), shift_num_bytes);
memcpy(&tdi_store, tdi_buf_temp + (current_bit / 8), shift_num_bytes);
// Shift data out and copy to output buffer
status = xil_xvc_shift_bits(db_ptr, tms_store, tdi_store, &tdo_store);
if (status) {
goto cleanup;
}
memcpy(tdo_buf_temp + (current_bit / 8), &tdo_store, shift_num_bytes);
current_bit += shift_num_bits;
}
if (copy_to_user((void *)xvc_obj.tdo_buf, tdo_buf_temp, num_bytes)) {
status = -EFAULT;
goto cleanup;
}
cleanup:
if (tms_buf_temp) kfree(tms_buf_temp);
if (tdi_buf_temp) kfree(tdi_buf_temp);
if (tdo_buf_temp) kfree(tdo_buf_temp);
return status;
}
long xil_xvc_readprops(const struct db_config* db_config, const char __user* arg) {
struct xil_xvc_properties xvc_props_obj;
if (!db_config) {
return -EINVAL;
}
xvc_props_obj.debug_bridge_base_addr = db_config->base_addr;
xvc_props_obj.debug_bridge_size = db_config->size;
strcpy(xvc_props_obj.debug_bridge_compat_string, DEBUG_BRIDGE_COMPAT_STRING);
if (copy_to_user((void *)arg, &xvc_props_obj, sizeof(xvc_props_obj))) {
return -ENOMEM;
}
return 0;
}
xvc_driver_base.c
c
/*
* Xilinx XVC Driver
* Copyright (C) 2019 Xilinx Corporation
*
* 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.
*/
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/io.h>
#include <linux/mod_devicetable.h>
#include "xvc_driver.h"
#include "xvc_user_config.h"
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Max Heimer <maxh@xilinx.com>");
MODULE_DESCRIPTION("XVC Debug Register Access");
MODULE_VERSION("0.1.0");
static dev_t xvc_ioc_dev_region;
static struct class* xvc_dev_class = NULL;
static struct cdev xvc_char_ioc_dev;
#ifndef _XVC_USER_CONFIG_H
#define CONFIG_COUNT 1
#define GET_DB_BY_RES 1
static struct resource *db_res = NULL;
#endif /* _XVC_USER_CONFIG_H */
static void __iomem * db_ptrs[CONFIG_COUNT];
static void xil_xvc_cleanup(void) {
printk(KERN_INFO LOG_PREFIX "Cleaning up resources...\n");
if (!IS_ERR(xvc_dev_class)) {
class_destroy(xvc_dev_class);
xvc_dev_class = NULL;
if (xvc_char_ioc_dev.owner != NULL) {
cdev_del(&xvc_char_ioc_dev);
}
unregister_chrdev_region(xvc_ioc_dev_region, CONFIG_COUNT);
}
}
long char_ctrl_ioctl(struct file *file_p, unsigned int cmd, unsigned long arg) {
long status = 0;
unsigned long irqflags = 0;
int char_index = iminor(file_p->f_path.dentry->d_inode) - MINOR(xvc_ioc_dev_region);
spin_lock_irqsave(&file_p->f_path.dentry->d_inode->i_lock, irqflags);
switch (cmd) {
case XDMA_IOCXVC:
status = xil_xvc_ioctl((unsigned char*)(db_ptrs[char_index]), (void __user *)arg);
break;
case XDMA_RDXVC_PROPS:
{
#ifndef GET_DB_BY_RES
struct db_config config_info = db_configs[char_index];
#else
struct db_config config_info = {
.name = NULL,
.base_addr = db_res ? db_res->start : 0,
.size = db_res ? resource_size(db_res) : 0,
};
#endif
status = xil_xvc_readprops(&config_info, (void __user*)arg);
break;
}
default:
status = -ENOIOCTLCMD;
break;
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 1, 0)
mmiowb();
#endif
spin_unlock_irqrestore(&file_p->f_path.dentry->d_inode->i_lock, irqflags);
return status;
}
static struct file_operations xil_xvc_ioc_ops = {
.owner = THIS_MODULE,
.unlocked_ioctl = char_ctrl_ioctl
};
int probe(struct platform_device* pdev) {
int status;
int i;
unsigned int use_index = CONFIG_COUNT > 1;
dev_t ioc_device_number;
char ioc_device_name[32];
struct device* xvc_ioc_device = NULL;
if (!xvc_dev_class) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
xvc_dev_class = class_create(XVC_DRIVER_NAME);
#else
xvc_dev_class = class_create(THIS_MODULE, XVC_DRIVER_NAME);
#endif
if (IS_ERR(xvc_dev_class)) {
xil_xvc_cleanup();
dev_err(&pdev->dev, "unable to create class\n");
return PTR_ERR(xvc_dev_class);
}
cdev_init(&xvc_char_ioc_dev, &xil_xvc_ioc_ops);
xvc_char_ioc_dev.owner = THIS_MODULE;
status = cdev_add(&xvc_char_ioc_dev, xvc_ioc_dev_region, CONFIG_COUNT);
if (status != 0) {
xil_xvc_cleanup();
dev_err(&pdev->dev, "unable to add char device\n");
return status;
}
}
for (i = 0; i < CONFIG_COUNT; ++i) {
if (db_ptrs[i] == NULL) {
#ifndef GET_DB_BY_RES
const char *name = db_configs[i].name;
unsigned long db_addr = db_configs[i].base_addr;
unsigned long db_size = db_configs[i].size;
#else
const char *name = NULL;
unsigned long db_addr = 0;
unsigned long db_size = 0;
#endif
if (name && name[0]) {
sprintf(ioc_device_name, "%s_%s", XVC_DRIVER_NAME, name);
} else if (use_index) {
sprintf(ioc_device_name, "%s_%d", XVC_DRIVER_NAME, i);
} else {
sprintf(ioc_device_name, "%s", XVC_DRIVER_NAME);
}
ioc_device_number = MKDEV(MAJOR(xvc_ioc_dev_region), MINOR(xvc_ioc_dev_region) + i);
xvc_ioc_device = device_create(xvc_dev_class, NULL, ioc_device_number, NULL, ioc_device_name);
if (IS_ERR(xvc_ioc_device)) {
printk(KERN_WARNING LOG_PREFIX "Failed to create device %s", ioc_device_name);
xil_xvc_cleanup();
dev_err(&pdev->dev, "unable to create the device\n");
return status;
} else {
printk(KERN_INFO LOG_PREFIX "Created device %s", ioc_device_name);
}
#ifndef GET_DB_BY_RES
db_ptrs[i] = ioremap(db_addr, db_size);
#else
db_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (db_res) {
db_addr = db_res->start;
db_size = resource_size(db_res);
}
db_ptrs[i] = devm_ioremap_resource(&pdev->dev, db_res);
#endif
if (!db_ptrs[i] || IS_ERR(db_ptrs[i])) {
printk(KERN_ERR LOG_PREFIX "Failed to remap debug bridge memory at offset 0x%lX, size %lu", db_addr, db_size);
return -ENOMEM;
} else {
printk(KERN_INFO LOG_PREFIX "Mapped debug bridge at offset 0x%lX, size 0x%lX", db_addr, db_size);
}
}
}
return 0;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0)
static int remove(struct platform_device* pdev)
#else
static void remove(struct platform_device* pdev)
#endif
{
int i;
dev_t ioc_device_number;
if (pdev) {
for (i = 0; i < CONFIG_COUNT; ++i) {
if (db_ptrs[i]) {
#ifndef GET_DB_BY_RES
unsigned long db_addr = db_configs[i].base_addr;
unsigned long db_size = db_configs[i].size;
#else
unsigned long db_addr = 0;
unsigned long db_size = 0;
if (db_res) {
db_addr = db_res->start;
db_size = resource_size(db_res);
}
#endif
printk(KERN_INFO LOG_PREFIX "Unmapping debug bridge at offset 0x%lX, size %lu", db_addr, db_size);
#ifndef GET_DB_BY_RES
iounmap(db_ptrs[i]);
#else
// devm_ioremap_resource is managed by the kernel and undone on driver detach.
#endif
db_ptrs[i] = NULL;
ioc_device_number = MKDEV(MAJOR(xvc_ioc_dev_region), MINOR(xvc_ioc_dev_region) + i);
device_destroy(xvc_dev_class, ioc_device_number);
printk(KERN_INFO LOG_PREFIX "Destroyed device number %u (user config %i)", ioc_device_number, i);
}
}
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0)
return 0;
#endif
}
static const struct of_device_id xvc_of_ids[] = {
{ .compatible = DEBUG_BRIDGE_COMPAT_STRING, },
{}
};
static struct platform_driver xil_xvc_plat_driver = {
.driver = {
.name = XVC_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = xvc_of_ids,
},
.probe = probe,
.remove = remove,
};
// --------------------------
// --------------------------
// Driver initialization code
// --------------------------
// --------------------------
static int __init xil_xvc_init(void) {
int err = 0;
printk(KERN_INFO LOG_PREFIX "Starting...\n");
// Register the character packet device major and minor numbers
err = alloc_chrdev_region(&xvc_ioc_dev_region, 0, CONFIG_COUNT, XVC_DRIVER_NAME);
if (err != 0) {
xil_xvc_cleanup();
printk(KERN_ERR LOG_PREFIX "unable to get char device region\n");
return err;
}
memset(db_ptrs, 0, sizeof(*db_ptrs));
return platform_driver_register(&xil_xvc_plat_driver);
}
static void __exit xil_xvc_exit(void) {
platform_driver_unregister(&xil_xvc_plat_driver);
xil_xvc_cleanup();
}
module_init(xil_xvc_init);
module_exit(xil_xvc_exit);
xvc_ioctl.h
c
/*
* Xilinx XVC Driver
* Copyright (C) 2019 Xilinx Corporation
*
* 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.
*/
#ifndef _XVC_IOCTL_H
#define _XVC_IOCTL_H
#include <linux/ioctl.h>
#define XIL_XVC_MAGIC 0x58564344 // "XVCD"
struct xil_xvc_ioc {
unsigned opcode;
unsigned length;
unsigned char* tms_buf;
unsigned char* tdi_buf;
unsigned char* tdo_buf;
};
struct xil_xvc_properties {
unsigned long debug_bridge_base_addr;
unsigned long debug_bridge_size;
char debug_bridge_compat_string[64];
};
#define XDMA_IOCXVC _IOWR(XIL_XVC_MAGIC, 1, struct xil_xvc_ioc)
#define XDMA_RDXVC_PROPS _IOR(XIL_XVC_MAGIC, 2, struct xil_xvc_properties)
#endif /* _XVC_IOCTL_H */
xvc_user_config.h
c
//空
xvc_user_config_example.h
c
/*
* Xilinx XVC Driver
* Copyright (C) 2019 Xilinx Corporation
*
* 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.
*/
#ifndef _XVC_USER_CONFIG_H
#define _XVC_USER_CONFIG_H
// debug bridge configuration
struct db_config {
const char* name;
unsigned long base_addr;
unsigned long size;
};
/*
* Modify the macros and structure below with customizations
* for the driver, if desired.
*
* XVC_DRIVER_NAME - name of the driver and character files
* DEBUG_BRIDGE_COMPAT_STRING - debug_bridge ".compatible" entry in device tree
*
* name - alias to append to character file name
* base_addr - debug_bridge base address from device tree
* size - debug_bridge size from device tree
*/
#define XVC_DRIVER_NAME "xilinx_xvc_driver"
#define DEBUG_BRIDGE_COMPAT_STRING "xlnx,xvc"
static const struct db_config db_configs[] = {
/////////////////////////////////////////////////////////
// The single debug tree entry below with an empty
// name modifier will create a character file called:
//
// /dev/xilinx_xvc_driver
//
/////////////////////////////////////////////////////////
// {
// .name = "",
// .base_addr = 0x80010000,
// .size = 0x10000,
// },
/////////////////////////////////////////////////////////
// For two debug trees in the same driver, you can
// uncomment and modify the entries below. If names are
// empty, only the index will be appended to the
// character file:
// /dev/xilinx_xvc_driver_0
// /dev/xilinx_xvc_driver_1
//
// In this example, since the names are not empty, the
// names "tree0" and "tree1" are appended to the
// character files as follows:
//
// /dev/xilinx_xvc_driver_tree0
// /dev/xilinx_xvc_driver_tree1
//
/////////////////////////////////////////////////////////
//
{
.name = "tree0",
.base_addr = 0x90020000,
.size = 0x10000,
},
{
.name = "tree1",
.base_addr = 0x90030000,
.size = 0x10000,
},
};
#define CONFIG_COUNT (sizeof(db_configs) / sizeof(*db_configs))
#endif /* _XVC_USER_CONFIG_H */
app源码
bash
minglie@DESKTOP-NTI9KM5:/mnt/e/ant_s9_fix_nand/XilinxVirtualCable/jtag/zynqMP/src/user$ ll
total 24
drwxrwxrwx 1 minglie minglie 4096 Jul 23 23:29 ./
drwxrwxrwx 1 minglie minglie 4096 Jul 23 22:05 ../
-rwxrwxrwx 1 minglie minglie 667 Jul 23 22:05 Makefile*
-rwxrwxrwx 1 minglie minglie 6408 Jul 23 22:05 verify_xilinx_xvc_driver.c*
-rwxrwxrwx 1 minglie minglie 13048 Jul 23 22:05 xvcServer.c*
Makefile
bash
export CROSS_COMPILE ?= aarch64-linux-gnu-
export ARCH ?= arm64
MYCC := $(CROSS_COMPILE)gcc
DRIVER := xilinx_xvc_driver
XVCSERVER := xvcServer
TEST := verify_$(DRIVER)
IOCTL_HDR_DIR := ../driver
.PHONY: ioctl
ioctl: $(XVCSERVER)_ioctl
.PHONY: mmap
mmap: $(XVCSERVER)_mmap
.PHONY: test
test: $(TEST)
.PHONY: all
all: ioctl mmap test
$(XVCSERVER)_ioctl: $(XVCSERVER).c
$(MYCC) -Wall -I$(IOCTL_HDR_DIR) -DUSE_IOCTL -o $@ $^
$(XVCSERVER)_mmap: $(XVCSERVER).c
$(MYCC) -Wall -o $@ $^
$(TEST): $(TEST).c
$(MYCC) -I$(IOCTL_HDR_DIR) -o $@ $^
.PHONY: clean
clean:
rm -f $(XVCSERVER)_mmap
rm -f $(XVCSERVER)_ioctl
rm -f $(TEST)
verify_xilinx_xvc_driver.c
c
/*
* Copyright (c) 2019 Xilinx, Inc. All rights reserved.
*
* XILINX CONFIDENTIAL PROPERTY
* This document contains proprietary information which is
* protected by copyright. All rights are reserved. No part of
* this document may be photocopied, reproduced or translated to
* another program language without prior written consent of
* XILINX Inc., San Jose, CA. 95124
*
* Xilinx, Inc.
* XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" AS A
* COURTESY TO YOU. BY PROVIDING THIS DESIGN, CODE, OR INFORMATION AS
* ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, APPLICATION OR
* STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS IMPLEMENTATION
* IS FREE FROM ANY CLAIMS OF INFRINGEMENT, AND YOU ARE RESPONSIBLE
* FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE FOR YOUR IMPLEMENTATION.
* XILINX EXPRESSLY DISCLAIMS ANY WARRANTY WHATSOEVER WITH RESPECT TO
* THE ADEQUACY OF THE IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO
* ANY WARRANTIES OR REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE
* FROM CLAIMS OF INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <getopt.h>
#include "xvc_ioctl.h"
static const char *char_path = "/dev/xilinx_xvc_driver";
#define BYTE_ALIGN(a) ((a + 7) / 8)
#define MIN(a, b) (a < b ? a : b)
#define MAX(a, b) (a > b ? a : b)
static unsigned findMaxBytes(unsigned *nbits_list)
{
unsigned max_bits = 0;
while(*nbits_list)
{
max_bits = MAX(max_bits, *nbits_list);
++nbits_list;
}
return BYTE_ALIGN(max_bits);
}
static void rotatePattern(unsigned char *pattern)
{
unsigned char c;
unsigned pat_nbytes = strlen(pattern);
c = pattern[0];
memmove(pattern, pattern + 1, pat_nbytes - 1);
pattern[pat_nbytes - 1] = c;
}
static int testXVC(int fd, struct xil_xvc_ioc *xvc_ioc, unsigned cmd_nbits, unsigned char *pattern)
{
unsigned char *tms_buf = xvc_ioc->tms_buf;
unsigned char *tdi_buf = xvc_ioc->tdi_buf;
unsigned char *tdo_buf = xvc_ioc->tdo_buf;
struct timeval stop, start;
long delta_us;
double mbps;
unsigned cmd_nbytes;
unsigned pat_nbytes;
unsigned fill_nbytes;
unsigned vb;
cmd_nbytes = BYTE_ALIGN(cmd_nbits);
pat_nbytes = strlen(pattern);
// setup tms_buf
memset(tms_buf, 0xff, cmd_nbytes);
// copy pattern into tdi_buf
fill_nbytes = 0;
while (fill_nbytes < cmd_nbytes)
{
unsigned nbytes = MIN(pat_nbytes, cmd_nbytes);
memcpy(tdi_buf + fill_nbytes, pattern, nbytes);
fill_nbytes += nbytes;
}
// reset tdo_buf
memset(tdo_buf, 0, cmd_nbytes);
// set up ioctl codes
xvc_ioc->opcode = 0x02; // 0x01 for normal, 0x02 for bypass
xvc_ioc->length = cmd_nbits;
// start timer
gettimeofday(&start, NULL);
// run the test
int ret = ioctl(fd, XDMA_IOCXVC, xvc_ioc);
if (ret < 0)
{
printf("Could not run the command test bitlen %d\n", cmd_nbits);
printf("Error: %s\n", strerror(errno));
return -1;
}
// stop timer
gettimeofday(&stop, NULL);
if (stop.tv_usec < start.tv_usec)
delta_us = 1000000 - start.tv_usec + stop.tv_usec;
else
delta_us = stop.tv_usec - start.tv_usec;
delta_us += 1000000 * (stop.tv_sec - start.tv_sec);
mbps = (double) cmd_nbits / (double)(delta_us);
// verify tdo
while (vb < cmd_nbits)
{
unsigned nbits = MIN(cmd_nbits - vb, 8);
unsigned mask = 0xFF;
unsigned index = vb / 8;
mask >>= (8 - nbits);
if ((tdi_buf[index] & mask) != (tdo_buf[index] & mask))
{
printf("Test Length: %d, pattern %s FAILURE\n", cmd_nbits, pattern);
printf("\tByte %d did not match (0x%02X != 0x%02X mask 0x%02X), pattern %s\n",
index, tdi_buf[index] & mask, tdo_buf[index] & mask, mask, pattern);
return -1;
}
vb += nbits;
}
printf("Test Length: %d, %ld us, %.2f Mbps SUCCESS\n", cmd_nbits, delta_us, mbps);
return 0;
}
int main(int argc, char **argv)
{
int fd;
int c;
struct xil_xvc_ioc xvc_ioc;
unsigned max_nbytes;
unsigned char pattern[] = "abcdefgHIJKLMOP";
// unsigned test_lens[] = {1, 4, 6, 12, 24, 32, 64, 89, 144, 233,
// 377, 610, 987, 1597, 2584, 4096, 0x2000, 0x800000, 0};
unsigned test_lens[] = {32, 0};
unsigned test_index = 0;
static struct option longopts[] = {
{ "device", required_argument, NULL, 'd' },
{ NULL, 0, NULL, 0 }
};
while ((c = getopt_long(argc, argv, "d:", longopts, NULL)) != -1) {
switch (c) {
case 'd':
char_path = optarg;
break;
case '?':
fprintf(stderr, "usage: %s [-d,--device <device>]\n", *argv);
return 1;
}
}
// try opening the driver
fd = open(char_path, O_RDWR | O_SYNC);
if (fd <= 0)
{
printf("Could not open driver at %s\n", char_path);
printf("Error: %s\n", strerror(errno));
return 0;
}
max_nbytes = findMaxBytes(test_lens);
// set up buffers with the maximum size to be tested
xvc_ioc.tms_buf = (unsigned char *) malloc(max_nbytes);
xvc_ioc.tdi_buf = (unsigned char *) malloc(max_nbytes);
xvc_ioc.tdo_buf = (unsigned char *) malloc(max_nbytes);
if (!xvc_ioc.tms_buf || !xvc_ioc.tdi_buf || !xvc_ioc.tdo_buf)
{
printf("Could not allocate %d bytes for buffers\n", max_nbytes);
printf("Error: %s\n", strerror(errno));
return 0;
}
// run tests
while (test_lens[test_index])
{
if (testXVC(fd, &xvc_ioc, test_lens[test_index], pattern) < 0)
return 0;
++test_index;
rotatePattern(pattern);
}
// if we get this far we must be good
printf("XVC Driver Verified Successfully!\n");
return 0;
}
xvcServer.c
c
/* This work, "xvcServer.c", is a derivative of "xvcd.c" (https://github.com/tmbinc/xvcd)
* by tmbinc, used under CC0 1.0 Universal (http://creativecommons.org/publicdomain/zero/1.0/).
* "xvcServer.c" is licensed under CC0 1.0 Universal (http://creativecommons.org/publicdomain/zero/1.0/)
* by Avnet and is used by Xilinx for XAPP1251.
*
* Description : XAPP1251 Xilinx Virtual Cable Server for Linux
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <pthread.h>
#include <getopt.h>
#include <ctype.h>
#define USE_IOCTL
#ifndef USE_IOCTL
#define MAP_SIZE 0x10000
static const char *UIO_PATH = "/dev/uio0";
typedef struct {
uint32_t length_offset;
uint32_t tms_offset;
uint32_t tdi_offset;
uint32_t tdo_offset;
uint32_t ctrl_offset;
} jtag_t;
#else /* USE_IOCTL */
#include <sys/ioctl.h>
#include "xvc_ioctl.h"
static const char *CHAR_DEV_PATH = "/dev/xilinx_xvc_driver";
#endif /* !USE_IOCTL */
static int verbose = 0;
static int XVC_PORT = 2542;
static int sread(int fd, void *target, int len) {
unsigned char *t = target;
while (len) {
int r = read(fd, t, len);
if (r <= 0)
return r;
t += r;
len -= r;
}
return 1;
}
#ifdef USE_IOCTL
static void hexdump(FILE *fp, const void *buff, unsigned int size)
{
unsigned int i;
const uint8_t *b = (uint8_t *)buff;
char ascii[17];
char str[2] = { 0x0, };
if (size == 0)
return;
for (i = 0; i < size; i++) {
if ((i & 0x0f) == 0x00) {
fprintf(fp, " %08x:", i);
memset(ascii, 0, sizeof(ascii));
}
fprintf(fp, " %02x", b[i]);
str[0] = isprint(b[i]) ? b[i] : '.';
str[1] = '\0';
strncat(ascii, str, sizeof(ascii) - 1);
if ((i & 0x0f) == 0x0f)
fprintf(fp, " | %s\n", ascii);
}
/* print trailing up to a 16 byte boundary. */
for (; i < ((size + 0xf) & ~0xf); i++) {
fprintf(fp, " ");
str[0] = ' ';
str[1] = '\0';
strncat(ascii, str, sizeof(ascii) - 1);
if ((i & 0x0f) == 0x0f)
fprintf(fp, " | %s\n", ascii);
}
fprintf(fp, "\n");
}
#endif
#ifndef USE_IOCTL
int handle_data(int fd, volatile jtag_t* ptr) {
#else /* USE_IOCTL */
int handle_data(int fd, int fd_ioctl) {
#endif /* !USE_IOCTL */
char xvcInfo[32];
unsigned int bufferSize = 2048;
sprintf(xvcInfo, "xvcServer_v1.0:%u\n", bufferSize);
do {
char cmd[16];
unsigned char buffer[bufferSize], result[bufferSize / 2];
memset(cmd, 0, 16);
if (sread(fd, cmd, 2) != 1)
return 1;
if (memcmp(cmd, "ge", 2) == 0) {
if (sread(fd, cmd, 6) != 1)
return 1;
memcpy(result, xvcInfo, strlen(xvcInfo));
if (write(fd, result, strlen(xvcInfo)) != strlen(xvcInfo)) {
perror("write");
return 1;
}
if (verbose) {
printf("%u : Received command: 'getinfo'\n", (int)time(NULL));
printf("\t Replied with %s\n", xvcInfo);
}
break;
} else if (memcmp(cmd, "se", 2) == 0) {
if (sread(fd, cmd, 9) != 1)
return 1;
memcpy(result, cmd + 5, 4);
if (write(fd, result, 4) != 4) {
perror("write");
return 1;
}
if (verbose) {
printf("%u : Received command: 'settck'\n", (int)time(NULL));
printf("\t Replied with '%.*s'\n\n", 4, cmd + 5);
}
break;
} else if (memcmp(cmd, "sh", 2) == 0) {
if (sread(fd, cmd, 4) != 1)
return 1;
if (verbose) {
printf("%u : Received command: 'shift'\n", (int)time(NULL));
}
} else {
fprintf(stderr, "invalid cmd '%s'\n", cmd);
return 1;
}
int len;
if (sread(fd, &len, 4) != 1) {
fprintf(stderr, "reading length failed\n");
return 1;
}
int nr_bytes = (len + 7) / 8;
if (nr_bytes * 2 > sizeof(buffer)) {
fprintf(stderr, "buffer size exceeded\n");
return 1;
}
if (sread(fd, buffer, nr_bytes * 2) != 1) {
fprintf(stderr, "reading data failed\n");
return 1;
}
memset(result, 0, nr_bytes);
if (verbose) {
printf("\tNumber of Bits : %d\n", len);
printf("\tNumber of Bytes : %d \n", nr_bytes);
printf("\n");
}
#ifndef USE_IOCTL
int bytesLeft = nr_bytes;
int bitsLeft = len;
int byteIndex = 0;
int tdi = 0;
int tms = 0;
int tdo = 0;
while (bytesLeft > 0) {
int shift_num_bytes;
int shift_num_bits = 32;
tms = 0;
tdi = 0;
tdo = 0;
if (bytesLeft < 4) {
shift_num_bits = bitsLeft;
}
shift_num_bytes = (shift_num_bits + 7) / 8;
memcpy(&tms, &buffer[byteIndex], shift_num_bytes);
memcpy(&tdi, &buffer[byteIndex + nr_bytes], shift_num_bytes);
ptr->length_offset = shift_num_bits;
ptr->tms_offset = tms;
ptr->tdi_offset = tdi;
ptr->ctrl_offset = 0x01;
/* Switch this to interrupt in next revision */
while (ptr->ctrl_offset) {}
tdo = ptr->tdo_offset;
memcpy(&result[byteIndex], &tdo, shift_num_bytes);
bytesLeft -= shift_num_bytes;
bitsLeft -= shift_num_bits;
byteIndex += shift_num_bytes;
if (verbose) {
printf("LEN : 0x%08x\n", shift_num_bits);
printf("TMS : 0x%08x\n", tms);
printf("TDI : 0x%08x\n", tdi);
printf("TDO : 0x%08x\n", tdo);
}
}
#else /* USE_IOCTL */
struct xil_xvc_ioc xvc_ioc;
xvc_ioc.opcode = 0x01;
xvc_ioc.length = len;
xvc_ioc.tms_buf = buffer;
xvc_ioc.tdi_buf = buffer + nr_bytes;
xvc_ioc.tdo_buf = result;
if (ioctl(fd_ioctl, XDMA_IOCXVC, &xvc_ioc) < 0) {
int errsv = errno;
fprintf(stderr, "xvc ioctl error: %s\n", strerror(errsv));
return errsv;
}
if (verbose > 1) {
fprintf(stderr, "TMS (%d bytes/%d bits):\n", nr_bytes, len);
hexdump(stderr, buffer, nr_bytes);
fprintf(stderr, "TDI (%d bytes/%d bits):\n", nr_bytes, len);
hexdump(stderr, buffer + nr_bytes, nr_bytes);
fprintf(stderr, "TDO (%d bytes/%d bits):\n", nr_bytes, len);
hexdump(stderr, result, nr_bytes);
}
#endif /* !USE_IOCTL */
if (write(fd, result, nr_bytes) != nr_bytes) {
perror("write");
return 1;
}
} while (1);
/* Note: Need to fix JTAG state updates, until then no exit is allowed */
return 0;
}
#ifdef USE_IOCTL
void display_driver_properties(int fd_ioctl) {
int ret = 0;
struct xil_xvc_properties props;
ret = ioctl(fd_ioctl, XDMA_RDXVC_PROPS, &props);
if (ret < 0) {
perror("failed to read XVC driver properties");
return;
}
printf("INFO: XVC driver character file: %s\n", CHAR_DEV_PATH);
printf("INFO: debug_bridge base address: 0x%lX\n", props.debug_bridge_base_addr);
printf("INFO: debug_bridge size: 0x%lX\n", props.debug_bridge_size);
printf("INFO: debug_bridge device tree compatibility string: %s\n\n", props.debug_bridge_compat_string);
}
#endif /* USE_IOCTL */
int main(int argc, char **argv) {
int i;
int s;
int c;
struct sockaddr_in address;
char hostname[256];
/* options descriptor */
static struct option longopts[] = {
{ "help", no_argument, NULL, '?' },
{ "verbose", no_argument, NULL, 'v' },
{ "port", required_argument, NULL, 'p' },
{ "device", required_argument, NULL, 'd' },
{ NULL, 0, NULL, 0 }
};
opterr = 0;
while ((c = getopt_long(argc, argv, "vp:d:", longopts, NULL)) != -1) {
switch (c) {
case 'v':
verbose++;
break;
case 'p':
XVC_PORT = atoi(optarg);
break;
case 'd':
#ifdef USE_IOCTL
CHAR_DEV_PATH = optarg;
#else
UIO_PATH = optarg;
#endif
break;
case '?':
fprintf(stderr, "usage: %s [-v,--verbose] [-p,--port <port>] "
"[-d,--device <device>]\n", *argv);
return 1;
}
}
#ifndef USE_IOCTL
int fd_uio;
volatile jtag_t* ptr = NULL;
fd_uio = open(UIO_PATH, O_RDWR);
if (fd_uio < 1) {
fprintf(stderr, "Failed to open uio: %s\n", UIO_PATH);
return -1;
}
ptr = (volatile jtag_t*) mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd_uio, 0);
if (ptr == MAP_FAILED) {
fprintf(stderr, "MMAP Failed\n");
return -1;
}
close(fd_uio);
#else /* USE_IOCTL */
int fd_ioctl;
fd_ioctl = open(CHAR_DEV_PATH, O_RDWR | O_SYNC);
if (fd_ioctl < 1) {
fprintf(stderr, "Failed to open xvc ioctl device driver: %s\n", CHAR_DEV_PATH);
return -1;
}
display_driver_properties(fd_ioctl);
#endif /* !USE_IOCTL */
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
return 1;
}
i = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof i);
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(XVC_PORT);
address.sin_family = AF_INET;
if (bind(s, (struct sockaddr*) &address, sizeof(address)) < 0) {
perror("bind");
return 1;
}
if (listen(s, 1) < 0) {
perror("listen");
return 1;
}
if (gethostname(hostname, sizeof(hostname)) != 0) {
perror("hostname lookup");
close(s);
return 1;
}
printf("INFO: To connect to this xvcServer instance, use url: TCP:%s:%u\n\n", hostname, XVC_PORT);
fd_set conn;
int maxfd = 0;
FD_ZERO(&conn);
FD_SET(s, &conn);
maxfd = s;
while (1) {
fd_set read = conn, except = conn;
int fd;
if (select(maxfd + 1, &read, 0, &except, 0) < 0) {
perror("select");
break;
}
for (fd = 0; fd <= maxfd; ++fd) {
if (FD_ISSET(fd, &read)) {
if (fd == s) {
int newfd;
socklen_t nsize = sizeof(address);
newfd = accept(s, (struct sockaddr*) &address, &nsize);
printf("connection accepted - fd %d\n", newfd);
if (newfd < 0) {
perror("accept");
} else {
printf("setting TCP_NODELAY to 1\n");
int flag = 1;
int optResult = setsockopt(newfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
if (optResult < 0)
perror("TCP_NODELAY error");
if (newfd > maxfd) {
maxfd = newfd;
}
FD_SET(newfd, &conn);
}
#ifndef USE_IOCTL
} else if (handle_data(fd, ptr)) {
#else /* USE_IOCTL */
} else if (handle_data(fd, fd_ioctl)) {
#endif /* !USE_IOCTL */
printf("connection closed - fd %d\n", fd);
close(fd);
FD_CLR(fd, &conn);
}
} else if (FD_ISSET(fd, &except)) {
printf("connection aborted - fd %d\n", fd);
close(fd);
FD_CLR(fd, &conn);
if (fd == s)
break;
}
}
}
#ifndef USE_IOCTL
munmap((void *) ptr, MAP_SIZE);
#else /* USE_IOCTL */
close(fd_ioctl);
#endif /* !USE_IOCTL */
return 0;
}