日前,工作中遇到了一个SATA热插拔的需求,客户想要在系统使用过程中能够通过一个开关来做控制,支持在不重启系统的情况下实现SATA硬盘的热插拔。
为了实现此功能,硬件设置如下:
1 2 3
| GPIO2_3用于一个船型开关,开关闭合(船型开关置“1”)时与GND短路。 SATA_PWRLED连接LED,指示SATA电源,点亮表示SATA供电。 SATA_STALED连接LED,指示硬盘运行状态。
|
概况来说,就是有以下资源:
1 2 3
| 一个DI输入,接了一个船型开关,可以通过拨动开关来获取01值 一个DO输出,控制着SATA硬盘的电源,并连接了一个LED灯 一个DO输出,连接了一个LED灯,可以配置成指示SATA硬盘的读写状态(与实现SATA热插拔功能无关)
|
软件上想要实现的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12
| 插入硬盘: 在SATA电源灯熄灭状态下,插入硬盘 拨动船型开关置“1” SATA电源灯亮起,状态灯闪烁 硬盘正常工作 拔出硬盘: 拨动船型开关置“0” 稍等片刻,待SATA电源灯熄灭 拔出硬盘即可
|
首先我在网上搜索了一下linux系统下的SATA硬盘热插拔的命令,找到了这篇文章《Linux上磁盘热插拔》。从这篇文章中获取了下面两个命令
1 2 3 4
| #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中断方法》。最终结合我们的需求,最终的程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #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
1 2 3 4 5 6 7 8 9 10 11
| [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上配置上以下模块
1
| CONFIG_LEDS_TRIGGER_DISK=y
|
另外在内核设备树中配置以下代码即可
1 2 3 4 5 6 7 8 9 10 11
| leds { status = "okay"; compatible = "gpio-leds";
SATA_STALED { label = "SATA_STALED"; gpios = <&gpio1 15 1>; linux,default-trigger = "disk-activity"; }; };
|