Linux系统实现SATA热插拔功能
日前,工作中遇到了一个SATA热插拔的需求,客户想要在系统使用过程中能够通过一个开关来做控制,支持在不重启系统的情况下实现SATA硬盘的热插拔。
为了实现此功能,硬件设置如下:
GPIO2_3用于一个船型开关,开关闭合(船型开关置“1”)时与GND短路。
SATA_PWRLED连接LED,指示SATA电源,点亮表示SATA供电。
SATA_STALED连接LED,指示硬盘运行状态。
概况来说,就是有以下资源:
一个DI输入,接了一个船型开关,可以通过拨动开关来获取01值
一个DO输出,控制着SATA硬盘的电源,并连接了一个LED灯
一个DO输出,连接了一个LED灯,可以配置成指示SATA硬盘的读写状态(与实现SATA热插拔功能无关)
软件上想要实现的逻辑如下:
插入硬盘:
在SATA电源灯熄灭状态下,插入硬盘
拨动船型开关置“1”
SATA电源灯亮起,状态灯闪烁
硬盘正常工作
拔出硬盘:
拨动船型开关置“0”
稍等片刻,待SATA电源灯熄灭
拔出硬盘即可
首先我在网上搜索了一下linux系统下的SATA硬盘热插拔的命令,找到了这篇文章《Linux上磁盘热插拔》。从这篇文章中获取了下面两个命令
#SATA硬盘热拔,删除该设备
echo 1 > /sys/bus/scsi/drivers/sd/0\:0\:0\:0/delete
#SATA硬盘热插,通过扫描scsi总线实现热插功能
echo "- - -" > /sys/class/scsi_host/host0/scan
我在板卡上测试了一下,确实可以通过上面的命令来实现SATA的热插拔,那么下面的问题就是如何通过一个船型开关来控制执行这两条命令。
最简单的方法就是写一个shell脚本,通过不断轮询船型开关的DI输入状态来执行这两条命令。但是这样如果想要保持及时性,就会大量的占用CPU资源,至少会导致一个CPU核心一直处于满负载状态。
这个时候我们当然就想到了使用中断来实现此功能,在内核中注册一个驱动,使用等待队列的知识,然后在文件系统中使用阻塞读即可。
后来在网上搜索方法,我们找到了一个文件系统层使用CPU中断的方法,文件为《linux用户态使用gpio中断方法》。最终结合我们的需求,最终的程序如下:
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int pov;
int ov = 1;
system("echo both > /sys/class/gpio/gpio451/edge");
int fd=open("/sys/class/gpio/gpio451/value",O_RDONLY);
if(fd<0)
{
perror("open '/sys/class/gpio/gpio451/value' failed!\n");
return -1;
}
struct pollfd fds[1];
fds[0].fd=fd;
fds[0].events=POLLPRI;
while(1)
{
if(poll(fds,1,-1)==-1)
{
perror("poll failed!\n");
return -1;
}
if(fds[0].revents&POLLPRI)
{
if(lseek(fd,0,SEEK_SET)==-1)
{
perror("lseek failed!\n");
return -1;
}
char buffer[16];
int len;
if((len=read(fd,buffer,sizeof(buffer)))==-1)
{
perror("read failed!\n");
return -1;
}
buffer[len]=0;
if((int)buffer[0] == 48)
{
if(!ov)
{
ov = 1;
system("echo Connect SATA ...");
system("echo 1 > /sys/class/leds/SATA_PWRON/brightness");
sleep(1);
system("echo '- - -' > /sys/class/scsi_host/host0/scan");
}
}
else
{
if(ov)
{
ov = 0;
system("echo DISCONNECT SATA ...");
system("umount /dev/sda1");
system("echo 1 > /sys/bus/scsi/drivers/sd/0:0:0:0/delete");
system("echo 0 > /sys/class/leds/SATA_PWRON/brightness");
}
}
}
}
return 0;
}
这里需要注意的一些逻辑是:
在执行SATA上电的操作后,最好等待一些时间再执行SATA总线的扫描操作
在执行SATA的删除操作前,最好先将SATA卸载掉
当然上面写的程序也不是完美逻辑的,只是为以后结果类似问题提供了一些参考。
最后,在文件系统创建自启服务如下:
rootfs_ubuntu_bionic_arm64/etc/systemd/system/sata_deamon.service
[Unit]
Description = sata deamon
[Service]
ExecStart = /usr/sbin/sata_deamon
Restart = always
ExecStartPre=/bin/sleep 5
Type = simple
[Install]
WantedBy = multi-user.target
另外,如果想使用一个led灯来指示SATA的硬盘的读写状态的话,可以在内核的defconfig上配置上以下模块
CONFIG_LEDS_TRIGGER_DISK=y
另外在内核设备树中配置以下代码即可
leds {
status = "okay";
compatible = "gpio-leds";
SATA_STALED {
label = "SATA_STALED";
gpios = <&gpio1 15 1>;
linux,default-trigger = "disk-activity";
};
};