如何修复在 Linux 内核 SPI driver 中始终无法通过验证的 SPI driver
How to fix an SPI driver that always fails validation in the Linux kernel SPI driver
我 运行 嵌入式 Linux (4.14.16) 使用 Yocto (Pyro) 构建。我 运行 在具有 i.MX6DL 且 SPI 连接到 FPGA(Xilinx Artix 7)的定制板上。我目前正在编写作为抽象层一部分的 class,因此这段代码位于 Linux driver 之上。它不是 Linux 设备 driver。 SPI 有效;我可以使用 shell 脚本对 FPGA 进行编程,如果将数据回显到 /dev/spi1.0(FPGA 将 SPI 输出到 header,我有一个分析器,则可以查看 SPI 流量连接到)。
问题是当我使用 driver 尝试读取 FPGA 中的寄存器时,它没有发送任何内容; SPI 传输不会发生。
我深入研究了 Linux 中的 spidev 和 spi drivers,我看到它在 drivers/spi/spi.c 中的 __spi_validate
调用失败了在 /* check transfer rx_nbits */
评论下。是什么控制这些位?我们板上的所有内容都是每个时钟的单个数据位,我们没有 quad-spi.
这里是有问题的代码:
#include "os/drivers/buses/linuxos/spi_driver.h"
#include <fcntl.h>
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
namespace os
{
namespace drivers
{
namespace buses
{
namespace linuxos
{
spi_driver::spi_driver(int bus_id, int cs_index, std::uint32_t speed_bps) :
m_speed_bps(speed_bps)
{
std::stringstream descriptor;
descriptor << "/dev/spidev" << bus_id << '.' << cs_index;
m_device_file_descriptor = descriptor.str();
}
bool spi_driver::transfer(const unsigned char *out_data, unsigned char *in_data, size_t size_in_bytes)
{
int spi_file_handle = open(m_device_file_descriptor.c_str(), O_RDWR);
bool success = (spi_file_handle >= 0);
if (success)
{
printf("spidev opened\n");
struct spi_ioc_transfer transfer_parameters;
transfer_parameters.tx_buf = reinterpret_cast<unsigned long>(out_data);
transfer_parameters.rx_buf = reinterpret_cast<unsigned long>(in_data);
transfer_parameters.len = size_in_bytes;
transfer_parameters.speed_hz = m_speed_bps;
transfer_parameters.bits_per_word = 0;
// transfer_parameters.cs_change = 0;
// transfer_parameters.delay_usecs = 0;
int ioctl_return = ioctl(spi_file_handle, SPI_IOC_MESSAGE(1), &transfer_parameters);
printf("spidev ioctl returned %d\n", ioctl_return);
success = (ioctl_return > 0);
printf("Received data: ");
for (unsigned int i = 0; i < size_in_bytes; i++)
{
printf("%02x ", in_data[i]);
}
printf("\n");
}
close(spi_file_handle);
return success;
}
}
}
}
}
我不确定它是否相关(SPI 似乎可以工作...)但这是设备树的 SPI 部分。
&ecspi2 {
cs-gpios = <&gpio5 12 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi2 &pinctrl_ecspi2_cs>;
status = "okay";
simulator-fpga {
compatible = "mi,simulator-fpga";
spi-max-frequency = <8000000>;
reg = <0>;
};
};
mi,simulator-fpga 已添加到通用 spidev driver 的兼容字符串中,因为如果直接使用 spidev 会打印错误, 但它是通用的 spidev 设备。
我确实尝试过只使用 Linux 读写函数,它起作用了(我在 SPI 分析器上看到了流量),但我需要全双工传输,这无法通过该方法完成。
编辑:如果有人想知道这些 printf 语句打印出什么,下面是我从中得到的:
spidev opened
spidev ioctl returned -1
Received data: 00 00 00 00 00 00 00 00
接收到的数据是要发送的所需消息的正确长度。我不确定为什么 return 值会得到 -1,内核中 spi.c 中抛出的错误是 -22(EINVAL/Invalid 参数),如上所述。
所以我不确定为什么需要这样做,但这个版本的驱动程序似乎可以工作。我添加了 this example. 中的所有额外 ioctl
虽然我的 FPGA 仍然不喜欢输出(即使它在 3.14 上运行良好),但它在逻辑分析器和协议分析器上看起来是正确的。
#include "os/drivers/buses/linuxos/spi_driver.h"
#include <fcntl.h>
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
namespace os
{
namespace drivers
{
namespace buses
{
namespace linuxos
{
spi_driver::spi_driver(int bus_id, int cs_index, std::uint32_t speed_bps) :
m_speed_bps(speed_bps)
{
std::stringstream descriptor;
descriptor << "/dev/spidev" << bus_id << '.' << cs_index;
m_device_file_descriptor = descriptor.str();
}
bool spi_driver::transfer(const unsigned char *out_data, unsigned char *in_data, size_t size_in_bytes)
{
int spi_file_handle = open(m_device_file_descriptor.c_str(), O_RDWR);
bool success = (spi_file_handle >= 0);
const std::uint8_t mode = 3;
const std::uint8_t bits = 8;
int ioctl_return = 0;
if (success)
{
ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_MODE, &mode);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_MODE, &mode);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_MAX_SPEED_HZ, &m_speed_bps);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_MAX_SPEED_HZ, &m_speed_bps);
if (ioctl_return != 0)
success = false;
}
if (success)
{
struct spi_ioc_transfer tr;
tr.tx_buf = reinterpret_cast<unsigned long>(out_data);
tr.rx_buf = reinterpret_cast<unsigned long>(in_data);
tr.len = size_in_bytes;
tr.delay_usecs = 0;
tr.speed_hz = 0;
tr.bits_per_word = 0;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_MESSAGE(1), &tr);
success = (ioctl_return != 1);
}
close(spi_file_handle);
return success;
}
}
}
}
}
我 运行 嵌入式 Linux (4.14.16) 使用 Yocto (Pyro) 构建。我 运行 在具有 i.MX6DL 且 SPI 连接到 FPGA(Xilinx Artix 7)的定制板上。我目前正在编写作为抽象层一部分的 class,因此这段代码位于 Linux driver 之上。它不是 Linux 设备 driver。 SPI 有效;我可以使用 shell 脚本对 FPGA 进行编程,如果将数据回显到 /dev/spi1.0(FPGA 将 SPI 输出到 header,我有一个分析器,则可以查看 SPI 流量连接到)。
问题是当我使用 driver 尝试读取 FPGA 中的寄存器时,它没有发送任何内容; SPI 传输不会发生。
我深入研究了 Linux 中的 spidev 和 spi drivers,我看到它在 drivers/spi/spi.c 中的 __spi_validate
调用失败了在 /* check transfer rx_nbits */
评论下。是什么控制这些位?我们板上的所有内容都是每个时钟的单个数据位,我们没有 quad-spi.
这里是有问题的代码:
#include "os/drivers/buses/linuxos/spi_driver.h"
#include <fcntl.h>
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
namespace os
{
namespace drivers
{
namespace buses
{
namespace linuxos
{
spi_driver::spi_driver(int bus_id, int cs_index, std::uint32_t speed_bps) :
m_speed_bps(speed_bps)
{
std::stringstream descriptor;
descriptor << "/dev/spidev" << bus_id << '.' << cs_index;
m_device_file_descriptor = descriptor.str();
}
bool spi_driver::transfer(const unsigned char *out_data, unsigned char *in_data, size_t size_in_bytes)
{
int spi_file_handle = open(m_device_file_descriptor.c_str(), O_RDWR);
bool success = (spi_file_handle >= 0);
if (success)
{
printf("spidev opened\n");
struct spi_ioc_transfer transfer_parameters;
transfer_parameters.tx_buf = reinterpret_cast<unsigned long>(out_data);
transfer_parameters.rx_buf = reinterpret_cast<unsigned long>(in_data);
transfer_parameters.len = size_in_bytes;
transfer_parameters.speed_hz = m_speed_bps;
transfer_parameters.bits_per_word = 0;
// transfer_parameters.cs_change = 0;
// transfer_parameters.delay_usecs = 0;
int ioctl_return = ioctl(spi_file_handle, SPI_IOC_MESSAGE(1), &transfer_parameters);
printf("spidev ioctl returned %d\n", ioctl_return);
success = (ioctl_return > 0);
printf("Received data: ");
for (unsigned int i = 0; i < size_in_bytes; i++)
{
printf("%02x ", in_data[i]);
}
printf("\n");
}
close(spi_file_handle);
return success;
}
}
}
}
}
我不确定它是否相关(SPI 似乎可以工作...)但这是设备树的 SPI 部分。
&ecspi2 {
cs-gpios = <&gpio5 12 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi2 &pinctrl_ecspi2_cs>;
status = "okay";
simulator-fpga {
compatible = "mi,simulator-fpga";
spi-max-frequency = <8000000>;
reg = <0>;
};
};
mi,simulator-fpga 已添加到通用 spidev driver 的兼容字符串中,因为如果直接使用 spidev 会打印错误, 但它是通用的 spidev 设备。
我确实尝试过只使用 Linux 读写函数,它起作用了(我在 SPI 分析器上看到了流量),但我需要全双工传输,这无法通过该方法完成。
编辑:如果有人想知道这些 printf 语句打印出什么,下面是我从中得到的:
spidev opened
spidev ioctl returned -1
Received data: 00 00 00 00 00 00 00 00
接收到的数据是要发送的所需消息的正确长度。我不确定为什么 return 值会得到 -1,内核中 spi.c 中抛出的错误是 -22(EINVAL/Invalid 参数),如上所述。
所以我不确定为什么需要这样做,但这个版本的驱动程序似乎可以工作。我添加了 this example. 中的所有额外 ioctl 虽然我的 FPGA 仍然不喜欢输出(即使它在 3.14 上运行良好),但它在逻辑分析器和协议分析器上看起来是正确的。
#include "os/drivers/buses/linuxos/spi_driver.h"
#include <fcntl.h>
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
namespace os
{
namespace drivers
{
namespace buses
{
namespace linuxos
{
spi_driver::spi_driver(int bus_id, int cs_index, std::uint32_t speed_bps) :
m_speed_bps(speed_bps)
{
std::stringstream descriptor;
descriptor << "/dev/spidev" << bus_id << '.' << cs_index;
m_device_file_descriptor = descriptor.str();
}
bool spi_driver::transfer(const unsigned char *out_data, unsigned char *in_data, size_t size_in_bytes)
{
int spi_file_handle = open(m_device_file_descriptor.c_str(), O_RDWR);
bool success = (spi_file_handle >= 0);
const std::uint8_t mode = 3;
const std::uint8_t bits = 8;
int ioctl_return = 0;
if (success)
{
ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_MODE, &mode);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_MODE, &mode);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_MAX_SPEED_HZ, &m_speed_bps);
if (ioctl_return != 0)
success = false;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_MAX_SPEED_HZ, &m_speed_bps);
if (ioctl_return != 0)
success = false;
}
if (success)
{
struct spi_ioc_transfer tr;
tr.tx_buf = reinterpret_cast<unsigned long>(out_data);
tr.rx_buf = reinterpret_cast<unsigned long>(in_data);
tr.len = size_in_bytes;
tr.delay_usecs = 0;
tr.speed_hz = 0;
tr.bits_per_word = 0;
ioctl_return = ioctl(spi_file_handle, SPI_IOC_MESSAGE(1), &tr);
success = (ioctl_return != 1);
}
close(spi_file_handle);
return success;
}
}
}
}
}