练习使用Redd综合工厂的定制轮胎

上一篇文章中,我们研究了在Redd大楼中管理一千个小事情的理论,但是为了不增加数量,我们下一次推迟了实践。 进行实际实验的时候到了。 那些不使用Redd复合体的人也可以在本文中找到有用的知识,即从Linux OS向USB驱动器发送供应商命令的方法,因为如前所述,复合体中的STM32控制器执行SD读取器的功能,即开车。




通过命令系统进行驱动器分类


使用驱动器时,应区分物理接口和命令系统。 特别是CD / DVD / BD驱动器和其他光学器件。 传统上,它们连接到SATA电缆(以前称为IDE)。 但特别是在这条线上,只有PACKET命令在运行期间运行,在该数据块中放置了根据完全不同的原理编码的命令(我们将很快找出哪个命令)。 因此,现在我们将不多谈论电线,而是谈论其中运行的团队。 我知道三种用于驱动器的常用命令系统。

  • MMC SD卡可以理解。 老实说,对我来说,这是最神秘的命令系统。 看起来,如何提交它们很明显,但是如何在不仔细阅读包含大量过渡图的文档的情况下管理驱动器,我总是感到困惑。 幸运的是,今天这并不困扰我们,因为尽管我们使用SD卡,但“黑匣子”模式下的STM32控制器仍可使用它。
  • ATA 最初,这些命令在IDE总线上运行,然后在SATA上运行。 一个很棒的命令系统,但是今天我们也只提到它的存在。
  • SCSI 该命令系统可用于多种设备。 考虑将其用于驱动器中。 今天,这里的SCSI团队首先沿着SAS总线运行(顺便说一下,甚至具有SAS接口的SSD现在也很流行)。 奇怪的是,物理连接到SATA总线的光盘驱动器也可以通过SCSI命令工作。 根据大容量存储设备标准工作时,在USB总线上,命令也采用SCSI格式。 STM32微控制器通过USB总线连接到Redd复合系统,也就是说,在我们的示例中,命令遵循以下路径:



从PC到控制器,通过USB,命令均为SCSI格式。 控制器根据MMC规则对命令进行代码转换,然后通过SDIO总线发送命令。 但是我们必须为PC编写一个程序,因此团队将我们保留为SCSI格式。 它们由大容量存储设备设备驱动程序准备,我们通过文件系统驱动程序与之通信。 是否可以将请求与其他设备混合用于这些请求? 让我们做对。

SCSI命令系统详细信息


如果您正式解决此问题,则可以在t10.org上获得SCSI标准的描述,但是我们会现实的。 没有人会自愿阅读它。 更准确地说,不是他的,而是他们的:存在着一大堆打开的文档和大量的关闭文档,只有极度的需求才能使您沉浸在该标准所编写的复杂语言中(顺便说一下,这适用于t13.org上的ATA标准)。 阅读真实驱动器的文档要容易得多。 它是用一种更生动的语言编写的,并且从其中切出了假想的但不是真正使用的部分。 在准备本文时,我遇到了希捷《 SCSI命令参考手册》中的一个相当新的文档(2016年)(直接链接www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf,但与往常一样,我不知道她会活多久。 我认为,如果有人想精通此命令系统,则应从本文档开始。 我们只记得SD读取器实现了该描述中命令的更小子集。

简单地说,一个长度为6到16个字节的命令单元被发送到驱动器。 数据块可以从PC到驱动器,也可以从驱动器到PC附加到命令块(SCSI标准还允许双向交换,但是对于通过USB的海量存储设备,只允许一个块,这意味着方向仅是一个)。 在指令块中,第一个字节始终是命令代码。 其余字节为其参数。 填充参数的规则仅由命令的实现细节描述。



最初,我在文章中插入了许多示例,但是后来我意识到它们使阅读变得困难。 因此,我建议每个人都将Seigate文档表119中的READ CAPACITY(10)命令的字段与同一文档表97中的READ(10)命令的字段进行比较(请参见上面的链接)。 谁没有找到任何连接-请勿惊慌。 这就是我想展示的。 除了零字节中的“命令”字段外,所有字段的目的仅取决于特定命令的细节。 您始终需要打开文档并研究其中其余字段的用途。

因此:

  • 为了与驱动器通信,您应该形成一个长度为6到16个字节的命令块(取决于命令的格式,确切的数字在其文档中指出)。
  • 最重要的是块的零字节:设置命令代码的是他。
  • 剩余的块字节没有明确的目的。 要了解如何填写它们,您应该打开特定团队的文档。
  • 可以将数据传输到驱动器或从驱动器传输的数据块可以附加到命令。

实际上,仅此而已。 我们了解了发出SCSI命令的规则。 现在我们可以提交它们,上面会有文档。 但是,如何在操作系统级别上做到这一点?

Linux SCSI命令


搜索目标设备


要发出命令,请打开磁盘设备。 让我们找到他的名字。 为此,我们将采用与有关串行端口文章中完全相同的方法。 让我们看一下/ dev目录中的“文件”列表(请记住,在Linux设备上也显示为文件,并且它们的列表使用相同的ls命令显示)。

今天,我们注意虚拟目录磁盘



我们看一下它的内容:



一组熟悉的嵌套目录! 我们正在尝试使用ls命令–l开关考虑by-id目录,有关串行端口的文章,该目录已经为我们所熟悉:



突出显示的单词说明一切。 这是一个包含Redd Complex的内部SD卡的驱动器。 太好了! 现在我们知道设备MIR_Redd_Internal_SD对应于设备/ dev / sdb和/ dev / sdb1 。 没有编号的是驱动器本身,我们将使用它,并且编号是位于插入介质中的文件系统。 就使用SD卡而言, / dev / sdb是读取器, / dev / sdb1是插入其中的卡上的文件系统。

操作系统发出命令的功能


通常,在任何操作系统中,设备的所有非标准操作都是通过直接请求驱动程序来完成的。 在Linux上,可以使用ioctl()函数发送此类请求。 我们的情况也不例外。 作为参数,我们传递sg.h头文件中描述的SG_IO请求。 此处还描述了包含请求参数的sg_io_hdr_t结构。 我不会给出完整的结构,因为并非所有字段都需要填写。 我只给出其中最重要的:

typedef struct sg_io_hdr { int interface_id; /* [i] 'S' for SCSI generic (required) */ int dxfer_direction; /* [i] data transfer direction */ unsigned char cmd_len; /* [i] SCSI command length ( <= 16 bytes) */ unsigned char mx_sb_len; /* [i] max length to write to sbp */ unsigned short int iovec_count; /* [i] 0 implies no scatter gather */ unsigned int dxfer_len; /* [i] byte count of data transfer */ void * dxferp; /* [i], [*io] points to data transfer memory or scatter gather list */ unsigned char * cmdp; /* [i], [*i] points to command to perform */ unsigned char * sbp; /* [i], [*o] points to sense_buffer memory */ unsigned int timeout; /* [i] MAX_UINT->no timeout (unit: millisec) */ 

描述注释中很好记录的那些字段( interface_id,dxfer_direction,timeout )是没有意义的。 这篇文章已经在增长。

cmd_len字段包含命令块中的字节数,而cmdp包含指向该块的指针。 没有命令就无法做,因此字节数必须为非零(从6到16)。

数据是可选的。 如果是,则在dxfer_len字段中指定所选缓冲区的长度,并在dxferp字段中指定指向该缓冲区的指针。 驱动器可以物理传输的数据少于指定的缓冲区大小。 传输方向在dxfer_direction字段中指定。 有效的USB海量存储设备值为SG_DXFER_NONE,SG_DXFER_TO_DEV,SG_DXFER_FROM_DEV 。 头文件中还有另外一件事,但是大容量存储设备标准不允许物理实现它。

您还可以请求返回扩展错误代码( SENSE )。 可以在Segate文档的第2.4节中找到。 分配的缓冲区的长度在mx_sb_len字段中指示,指向缓冲区本身的指针在sbp字段中指示。

如您所见,我上面讨论的所有内容都填充在此结构中(此外,您还可以获得有关错误的扩展信息)。 在此处阅读有关使用SG_IO请求的更多信息: sg.danny.cz/sg/sg_io.html

我们向驱动器发送标准命令


好了,我们确定了命令的格式,我们确定了将命令发送到哪个设备,我们确定了要调用的函数。 让我们尝试向我们的设备发送一些标准命令。 让它成为获取驱动器名称的命令。 这是在Sigeyt文档中描述的方式:



请注意,根据SCSI意识形态,标准命令中的所有字段均以Big Endian表示法(即,最高字节向前)填充。 因此,我们在缓冲区中填写的缓冲区长度不是“ 0x80,0x00”格式,而是“ 0x00,0x80”。 但这是标准命令。 在非标准情况下,一切皆有可能,您应始终参考说明。 实际上,只有命令代码( 12h )和我们必须填写的长度。 我们将请求页面为零,其余字段将保留,或者已过期,或者默认为零。 因此,将它们全部填充为零。

我们编写一个给出以下命令的程序:
 #include <cstdio> #include <stdint.h> #include <string.h> #include <fcntl.h> // open #include <unistd.h> // close #include <sys/ioctl.h> #include <scsi/scsi.h> #include <scsi/sg.h> int main() { printf("hello from SdAccessTest!\n"); int s_fd = open("/dev/sdb", O_NONBLOCK | O_RDWR); if (s_fd < 0) { printf("Cannot open file\n"); return -1; } sg_io_hdr_t header; memset(&header;, 0, sizeof(header)); uint8_t cmd12h[] = { 0x12,0x00,0x00,0x00,0x80,0x00}; uint8_t data[0x80]; uint8_t sense[0x80]; header.interface_id = 'S'; //  'S' //  header.cmd_len = sizeof(cmd12h); header.cmdp = cmd12h; //  header.dxfer_len = sizeof(data); header.dxferp = data; header.dxfer_direction = SG_DXFER_TO_FROM_DEV; //     header.mx_sb_len = sizeof(sense); header.sbp = sense; // header.timeout = 100; // 100  int res = ioctl(s_fd, SG_IO, &header;); close(s_fd); return 0; } 



上一篇文章中 ,我们已经讨论了如何在远程Redd设备上运行此类程序。 没错,第一次启动它,我立即收到调用open()函数的错误。 原来,用户默认情况下没有足够的权限来打开磁盘设备。 我写过很多书,我是哪位Linux专家,但是在网络上我设法找到了解决此问题的方法,您可以通过发出以下命令来更改设备的访问权限:

须藤chmod 666 / dev / sdb

但是,我的老板(他是该操作系统的杰出专家)后来指出,该解决方案在重新启动操作系统之前一直有效。 要确保获得权限,您需要将用户添加到磁盘组。

我们走这两条路径中的哪一条,但是在一切都完成之后,在断行(s_fd)上放置一个断点 并在开发环境中按时检查结果(因为该程序甚至不是一天的计划,这意味着如果开发环境可以向我们展示一切,我们就没有时间和精力来插入映射器)。 res的值为零。 因此,团队工作无误。



什么缓冲? 当我在转储地址中输入数据一词时,他们告诉我他们无法计算该值,我必须输入&数据; 。 奇怪,因为数据是指针,所以在Windows下调试时一切正常,但我只是注意到这一事实,它的工作原理如下:查看这样获得的结果:



没错,他们将驱动器的名称和版本退还给我们。 有关结果结构的格式的更多信息,请参见Segate文档(第3.6.2节,表59)。 感知缓冲区没有填满,但是对请求的IOCTL描述说,仅当发生错误并返回此缓冲区中的内容时,才填充该请求。 从字面上看: 感知数据(仅在“状态”为“检查条件”或((driver_status和DRIVER_SENSE)为true时使用)

Redd内部SD驱动器的自定义命令格式


现在,我们不仅研究了标准的简要描述,而且在实践中尝试了所有内容,并感觉到命令块是什么,我们已经可以显示命令格式,通过该命令格式,您可以调用“闪存”到组合板上的STM32控制器的非标准函数。 我从供应商特定命令范围的开头选择了命令代码。 等于0xC0。 传统上,在SCSI命令的描述中,写C0h 。 该命令的长度始终为10个字节。 团队的形式是统一的,并在下表中列出。

字节数预约时间
0命令代码C0h
1个子命令代码
2参数arg1。 设置为Little Endian表示法(低字节转发)
3
4
5
6参数arg2。 设置为Little Endian表示法(低字节转发)
7
8
9

如您所见,参数以Little Endian表示法给出。 这将允许您以结构的形式描述命令并直接访问其字段,而无需诉诸字节排列功能。 x86和x64体系结构上的对齐问题(结构中的双字偏移量不是四的倍数)是不值得的。

子命令代码由以下枚举描述:
 enum vendorSubCommands { subCmdSdEnable = 0, // 00 Switch SD card to PC or Outside subCmdSdPower, // 01 Switch Power of SD card On/Off subCmdSdReinit, // 02 Reinitialize SD card (for example, after Power Cycle) subCmdSpiFlashEnable, // 03 Switch SPI Flash to PC or Outside subCmdSpiFlashWritePage, // 04 Write Page to SPI Flash subCmdSpiFlashReadPage, // 05 Read Page from SPI Flash subCmdSpiFlashErasePage,// 06 Erase Pages on SPI Flash (4K block) subCmdRelaysOn, // 07 Switch relays On by mask subCmdRelaysOff, // 08 Switch relays off by mask subCmdRelaysSet, // 09 Set state of all relays by data subCmdFT4222_1_Reset, // 0A Activate Reset State or switch chip to normal mode subCmdFT4222_2_Reset, // 0B Activate Reset State or switch chip to normal mode subCmdFT4222_3_Reset, // 0C Activate Reset State or switch chip to normal mode subCmdFT4232_Reset, // 0D Activate Reset State or switch chip to normal mode subCmdFT2232_Reset, // 0E Activate Reset State or switch chip to normal mode subCmdMAX3421_Reset, // 0F Activate Reset State or switch chip to normal mode subCmdFT4222_1_Cfg, // 10 Write to CFG pins of FT4222_1 subCmdFT4222_2_Cfg, // 11 Write to CFG pins of FT4222_2 subCmdFT4222_3_Cfg, // 12 Write to CFG pins of FT4222_3 }; 

它们可以分为几组。

将设备切换到内部和外部模式


subCmdSdEnablesubCmdSpiFlashEnable命令分别切换SD卡和SPI闪存。 参数arg1传递以下值之一:

 enum enableMode { enableModeToPC = 0, enableModeOutside }; 

默认情况下,两个设备都连接到PC。

电源开关


SDIO协议在初始化期间需要进行大量操作。 有时将SD卡重置为初始状态很有用(例如,将其线路切换到外部连接器时)。 为此,请先关闭电源,然后再打开电源。 这可以使用subCmdSdPower命令完成 。 在参数arg1中,传递以下值之一:0-关闭电源,1-打开电源。 请记住要给时间给电源线上的电容器放电。

接通电源后,如果卡已连接到PC,则应重新初始化。 为此,请使用subCmdSdReinit命令(它没有参数)。

使用SPI闪存驱动器


如果SD卡作为完整驱动器连接到系统,则当前版本中的访问芯片非常有限。 您只能访问其单个页面(256个字节),一次只能访问一个页面。 微电路中的内存量使得即使在页面上工作时,该过程也不会花费很多时间,但是这种方法极大地简化了微控制器的“固件”。

subCmdSpiFlashReadPage命令读取页面。 地址在arg1参数中指定,要发送的页面数在arg2参数中指定。 但是在当前版本中,页面数应等于一。 该命令将返回256个字节的数据。

为她镜像的是subCmdSpiFlashWritePage命令。 关于她的论点也以同样的原则填写。 数据传输的方向是设备。

闪存的独特之处在于在记录过程中只能用零位替换单个位。 要将它们恢复为单个值,应擦除页面。 为此有一个subCmdSpiFlashErasePage命令。 的确,由于所用微电路的特性,要擦除的不是arg1参数中设置的单个页面,而是包含该页面的4 KB块。

固态继电器管理


该综合大楼有六个固态继电器。 有三个团队来管理它们。

subCmdRelaysSet-同时设置所有六个继电器的值。 在参数arg1中传递一个,该值的每个位对应于其自己的继电器(零位-索引为0的继电器,索引为1的第一位,等等)。 单个值使继电器闭合,零值使继电器断开。

当所有继电器作为一个组工作时,这种操作方法很好。 如果它们彼此独立工作,则使用这种方法必须启动一个缓冲区变量,该变量存储所有继电器的状态值。 如果不同的继电器由不同的程序控制,则存储合计值的问题将变得极为严重。 在这种情况下,可以使用其他两个命令:

subCmdRelaysOn-按掩码启用选定的继电器。 那些与arg1参数中的单位位相对应的继电器将被启用。 与掩码中的零相对应的继电器将保持其当前状态。

镜像它subCmdRelaysOff命令将通过掩码关闭选定的继电器。 与参数arg1中的单个位相对应的那些继电器将被关闭。 与掩码中的零相对应的继电器将保持其当前状态。

重置FTDI和Maxim控制器


要将复位信号发送到FTDI和Maxim微电路,请使用命令组subCmdFT4222_1_ResetsubCmdFT4222_2_ResetsubCmdFT4222_3_ResetsubCmdFT4232_ResetsubCmdFT2232_ResetsubCmdMAX3421_ 。 从它们的名称中,您可以看到它们通过复位信号控制哪些芯片。 正如我们之前所考虑的,FT4222桥在电路中有两个(它们的索引分别为1和2),另一个FT4222桥将数据传输到MAX3421芯片,我们将在下一篇文章中进行讨论。

参数arg1传递以下值之一:

 enum ResetState { resetStateActive =0, resetStateNormalOperation }; 

缺省情况下,所有网桥处于正常工作状态。 正如上一篇文章中已经提到的,我们自己不确定是否需要此功能,但是当无法直接访问设备时,最好能够远程重置所有内容。

FT4222芯片的切换配置线


FT4222芯片具有四种模式。 任何人都不太可能需要“ 00”以外的模式,但是如果您突然需要它,则可以使用subCmdFT4222_1_CfgsubCmdFT4222_2_CfgsubCmdFT4222_3_Cfg命令切换第一,第二和第三芯片。 CFG0和CFG1行的值在arg1参数的低两位设置。

向STM32控制器发出命令的实践经验


为了测试在实践中获得的理论资料,我们将尝试将SD卡拔出。 为此,请发出subCmdSdEnable命令,代码为0x00,参数enableModeOutside为代码0x01。 太好了 我们根据过去的经验重写程序,如下所示。

改写程序:
 #include <cstdio> #include <stdint.h> #include <string.h> #include <fcntl.h> // open #include <unistd.h> // close #include <sys/ioctl.h> #include <scsi/scsi.h> #include <scsi/sg.h> int main() { printf("hello from SdAccessTest!\n"); int s_fd = open("/dev/sdb", O_NONBLOCK | O_RDWR); if (s_fd < 0) { printf("Cannot open file\n"); return -1; } sg_io_hdr_t header; memset(&header;, 0, sizeof(header)); uint8_t cmdSdToOutside[] = { 0xC0,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; uint8_t cmdSdToPC[] = { 0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; uint8_t sense[32]; memset(sense, 0, sizeof(sense)); header.interface_id = 'S'; //  'S' //  header.cmd_len = sizeof(cmdSdToOutside); header.cmdp = cmdSdToOutside; //  ( ) header.dxfer_len = 0; header.dxferp = 0; header.dxfer_direction = SG_DXFER_NONE; //     header.mx_sb_len = sizeof(sense); header.sbp = sense; // header.timeout = 100; // 100  int res = ioctl(s_fd, SG_IO, &header;); //   header.cmdp = cmdSdToPC; res = ioctl(s_fd, SG_IO, &header;); close(s_fd); return 0; } 


我们将命令长度更改为十个字节,并删除了数据块。 好了,他们根据需要写下了带有参数的命令代码。 否则,一切保持不变。 我们开始……然后……什么都没有。 ioctl()函数返回错误。 原因在SG_IO命令文档中进行了描述。 事实是,我们给了Vendor Specific命令C0h ,而关于它们的字面意义如下:
sg驱动程序未提及的任何其他SCSI命令(操作码)都需要O_RDWR。 块层SG_IO ioctl中未提及的任何其他SCSI命令(操作码)都需要具有CAP_SYS_RAWIO功能的用户。

正如老板向我解释的那样(我只是在重复他的话), 功能值已分配给可执行文件。 因此,我必须以root身份登录来跟踪开发环境。 不是最好的解决方案,但至少是某些解决方案。 实际上,在Windows上, IOCTL_SCSI_PASS_THROUGH_DIRECT请求也需要管理员权限。 也许有人会在注释中提供一些建议,以解决这些跟踪问题,而无需采取如此繁琐的步骤,但是如果您 root注册了正确的功能 ,则可以在没有root的情况下运行已经编写的程序。 同时,在开发环境中更改用户名并在行上设置一个断点:

 int res = ioctl(s_fd, SG_IO, &header;); 

在调用ioctl()函数之前,我们先看一下存储设备列表:



调用ioctl()并再次查看列表:



该设备/ dev / sdb仍然存在(大致来说,这是SD卡读取器本身),而/ dev / sdb1不见了。 该设备对应于介质上的文件系统。 载体已从计算机断开连接-不再可见。 我们继续跟踪。 调用第二个ioctl()函数后,我们再次查看设备列表:



SD卡已重新连接至系统,因此/ dev / sdb1恢复原位。 实际上,我们了解了如何在Redd联合体中发布特定于供应商的命令并基于STM32微控制器来管理设备。 其他命令将留给读者进行独立研究。 您可以通过类似的方式控制其中一些的操作。 如果某些ftdi芯片进入复位状态,则相应的设备将从系统中消失。 继电器的操作和配置脚的控制将必须由测量仪器来控制。 好了,您可以通过使用后续的阅读控件来写页面来检查闪存驱动器的工作。

结论


我们研究了与Redd复杂系统中的FPGA不相关的两个大主题。 第三个仍然是-与MAX3421芯片配合使用,可实现USB 2.0 FS器件。 实际上,也有主机,但是有很多主机和主板。 该设备的功能将使综合大楼假装成USB闪存驱动器(发送“固件”的更新),USB键盘(控制外部单元)等。 我们将在下一篇文章中讨论此主题。

Source: https://habr.com/ru/post/zh-CN484706/


All Articles