一、热插拔
1.主题
热插拔TF卡时如何自动挂载到指定目录
2.问题背景
功能需求:产品正常使用过程中,热插拔TF卡,除了实时更新/dev下的设备节点,还要自动挂载文件系统到/mnt/sdcard/,和自动卸载/mnt/sdcard/,达到插上能直接用的效果。
达到的效果:插上tf卡,df命令有/dev/mmcblk1p1 120934096 8523164 106221672 7% /mnt/sdcard/mmcblk1p1拔出tf卡后消失。

3.问题分析
首先要实现自动挂载功能,先要确认你当前使用的热插拔方式。
热插拔主流有两种方式:
方式一:udev。
方式二:devtmpfs。
区别点:
-
方式二是内核里同步创建设备节点,方式一的是异步。所以devtmpfs要比udev方式更实时。
-
方式一需要在用户空间配合mdev或udevd工具,达到刷新设备节点的效果。方式二不需要。
-
方式一默认采用tmpfs文件系统,方式二是devtmpfs文件系统。
-
方式一可以用mdev或udevd工具,这些工具支持匹配脚本,达到自动挂载或管理。如mdev+mdev.conf、udevd+/etc/udev/rules.d/99-usb.rules。
4.解决办法
要实现自动挂载到/mnt/sdcard目录,有三种方法。
方法1:采用udev+mdev+mdev.conf。
方法2:采用udev+udevd+/etc/udev/rules.d/99-xxx.rules。功能强大,但不轻量,不建议。
方法3:不用udev,用devtmpfs+用户自己写程序监控uevent事件。
下面展示方法1的配置方式:
内核配置上:
-CONFIG_DEVTMPFS=y
-CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_UEVENT_HELPER_PATH="/sbin/mdev";
busybox或者buildroot需要配置上mdev;
mdev.conf文件:
#-.* 0:0 666 *{ date; set; echo; } >>/var/log/mdev.log
mmcblk[0-9] 0:0 0666 */etc/automount.sh
mmcblk[0-9]p[0-9] 0:0 0666 */etc/automount.sh
automount.sh脚本:
#!/bin/busybox ash
#
#remove
if [ "${ACTION}" == "remove" ]; then
MOUNTPOINT="$(grep -w "^/dev/${MDEV}" /proc/mounts | awk '{print $2}')"
[ -n "${MOUNTPOINT}" ] \
&& /bin/umount /dev/${MDEV} && rm -rf ${MOUNTPOINT}
exit 0
fi
# add
if [ "${ACTION}" == "add" ]; then
case ${MDEV} in
mmcblk[0-9])
[ -d "/sys/block/${MDEV}/${MDEV}p1" ] && exit 0
MOUNTPOINT=/mnt/sdcard
;;
mmcblk[0-9]p[0-9])
MOUNTPOINT=/mnt/sdcard
;;
*)
exit 0
;;
esac
for fstype in vfat exfat ntfs-3g ext4
do
if [ ${MOUNTPOINT} != "NULL" ]; then
mkdir -p ${MOUNTPOINT}
/bin/mount -t ${fstype} /dev/${MDEV} ${MOUNTPOINT}
exit 0
fi
done
fi
exit 0
注意:如果开了devtmpfs,内核会里会默认挂载到/dev目录,所以/etc/inittab里就不要::sysinit:/bin/mount -t tmpfs tmpfs /dev了,不然/dev会变成tmpfs文件系统,相当于没开devtmpfs。
二、将TF卡格式化为ext4
1. 查看磁盘分区信息
输入命令:cat /proc/partitions
root@Longan:/$ cat /proc/partitions
major minor #blocks name
179 0 7634944 mmcblk0
179 1 32768 mmcblk0p1
179 2 16384 mmcblk0p2
179 3 98304 mmcblk0p3
179 4 98304 mmcblk0p4
179 5 4194304 mmcblk0p5
179 6 3157999 mmcblk0p6
179 8 123473920 mmcblk1
179 9 123457536 mmcblk1p1
最后两条 mmcblk1 和 mmcblk1p1 表示我的TF卡的分区信息。
2. 卸载分区
输入命令:umount /mnt/mmcblk1p1
3. 使用fdisk工具对TF重新分区
输入命令:fdisk /dev/mmcblk1
ps:注意后面分区的名字,我之前就是因为这个名字输入错了所以好几次都是原格式,没有格式化成功!
Welcome to fdisk (util-linux 2.34).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): o # 输入o设置新分区为msdos格式
Created a new DOS disklabel with disk identifier 0x02359008.
Command (m for help): n # 输入n建立新分区
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p # 输入p表示新建的为主分区
Partition number (1-4, default 1):
First sector (2048-15407103, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-15407103, default 15407103):
Created a new partition 1 of type 'Linux' and of size 7.4 GiB.
Command (m for help): w # 输入w保存分区信息
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
4. 执行格式化
输入命令:mkfs.ext4 /dev/mmcblk1p1
root@Longan:~# mkfs.ext4 /dev/mmcblk1p1
mke2fs 1.45.5 (07-Jan-2020)
Creating filesystem with 1925632 4k blocks and 481440 inodes
Filesystem UUID: f022a57f-4d88-4972-9be7-7b0c1be395ff
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
格式化完成
自动化脚本,自动格式化为ext4
#!/bin/sh
#
# Start the mount udisk
#
script_dir=$(cd "$(dirname "$0")"; pwd)
case "$1" in
start)
if [ ! -b /dev/mmcblk1p1 ]; then
echo "SD card not exists"
exit 0
fi
# 如果 /etc/format_success 存在就跳过格式化
if [ -f /etc/format_success ]; then
echo "SD card has been formatted, skip format"
exit 0
fi
mkdir /mnt/sdcard/mmcblk1p1 -p
mount -t ext4 /dev/mmcblk1p1 /mnt/sdcard/mmcblk1p1 > /dev/null
mount | grep "/dev/mmcblk1p1" | grep "ext4" > /dev/null
is_mount_ext4=$?
if [ $is_mount_ext4 -eq 0 ]; then
echo "SD card is ext4"
exit 0
fi
echo "SD card is not ext4, format SD card"
umount /dev/mmcblk1p1 && sleep 2
mkfs.ext4 /dev/mmcblk1p1 -F
if [ $? -ne 0 ]; then
echo "SD card format failed"
exit 1
fi
mount /dev/mmcblk1p1 /mnt/sdcard/mmcblk1p1
echo "SD card format success" > /etc/format_success
sync
;;
stop)
;;
*)
echo "Usage: $0 {start}"
exit 1
;;
esac
exit 0
三、TF卡挂载异常
反复上电TF卡会偶尔出现挂载不上的情况

解决思路:检测到异常时复位该引脚,重新识别。所以我们在脚本中对该问题进行监测,如果TF卡为挂载、文件系统非ext4、只读模式以及读写错误都重新复位TF,重新识别。
在全志平台,可根据/sys/class/mmc_host/mmc1/device/mmc_host/mmc1/device/sunxi_sd_detect_pin_status的状态值判断TF卡是否插入,同时echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert可重新识别TF卡,触发重新挂载。
1、监测脚本:
#!/bin/bash
MOUNT_POINT="/mnt/sdcard/mmcblk1p1"
PARTITION="/dev/mmcblk1p1"
DEVICE="/dev/mmcblk1"
CHECK_INTERVAL=20
RESET_DELAY=5
DETECT_PIN_STATUS="/sys/class/mmc_host/mmc1/device/mmc_host/mmc1/device/sunxi_sd_detect_pin_status"
# 执行复位操作
reset_tf_card() {
echo "执行TF卡复位操作..."
echo 308 > /sys/class/gpio/export 2>/dev/null
# 等待GPIO就绪
sleep 1
echo out > /sys/class/gpio/gpio308/direction
echo 1 > /sys/class/gpio/gpio308/value
echo "当前磁盘挂载情况:"
df -h
sleep 1
echo 0 > /sys/class/gpio/gpio308/value
# 取消导出GPIO
echo 308 > /sys/class/gpio/unexport 2>/dev/null
echo "TF卡复位完成"
}
while true; do
current_time=$(date '+%H:%M:%S')
reset_performed=false
# 检查TF卡设备是否存在 - 通过检测引脚状态
pin_status=$(cat "$DETECT_PIN_STATUS" 2>/dev/null)
if [ "$pin_status" = "0" ]; then
echo "$current_time: 未插入TF卡设备"
sleep $CHECK_INTERVAL
continue
fi
# 使用df -T检查挂载状态和文件系统格式
mount_info=$(df -T "$MOUNT_POINT" 2>/dev/null | grep "$PARTITION")
# 检查条件
if [ -z "$mount_info" ]; then
echo "$current_time: 未挂载 - 执行重置"
reset_tf_card
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif [ "$(echo "$mount_info" | awk '{print $2}')" != "ext4" ]; then
echo "$current_time: 文件系统非ext4 - 执行重置"
reset_tf_card
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif mount | grep "$MOUNT_POINT" | grep -q "ro,"; then
echo "$current_time: 只读模式 - 执行重置"
reset_tf_card
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif ! touch "$MOUNT_POINT/.test" 2>/dev/null; then
echo "$current_time: 无法写入 - 执行重置"
reset_tf_card
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif dmesg | tail -30 | grep -i "$DEVICE" | grep -i "error\|I/O\|fail\|bad" >/dev/null; then
echo "$current_time: 检测到存储错误 - 执行重置"
reset_tf_card
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
else
echo "$current_time: 正常"
rm -f "$MOUNT_POINT/.test" 2>/dev/null
fi
# 等待挂载
if [ "$reset_performed" = true ]; then
echo "等待${RESET_DELAY}秒让TF卡重新初始化..."
sleep $RESET_DELAY
else
sleep $CHECK_INTERVAL
fi
done
2、测试脚本:
模拟车辆运行,放置于震动台进行测试。脚本会定期检查TF卡是否正常挂载,如果挂载正常,则重启MPU。如果TF卡挂载异常,则记录相关日志而不重启。
//重启 TF 震动测试
#!/bin/bash
# 功能:周期性检查TF卡挂载状态,仅在挂载正常时重启MPU,异常时记录日志
# 周期:约2分钟一次检查
# -------------------------- 配置参数 --------------------------
log_file="/mnt/tf_card_mount_log.txt" # 日志文件
mount_point="/mnt/sdcard/mmcblk1p1" # TF卡挂载路径
device="/dev/mmcblk1" # TF卡设备节点
reset_cmd="t527_mcu_reg_wr --mpu-reset" # MPU重启命令
reboot_wait=30 # 重启后等待稳定的时间(秒)
cycle_wait=120 # 两次检查的间隔时间(秒)
# 创建日志文件
if [ ! -f "$log_file" ]; then
echo "=== TF Card Mount & MPU Reboot Test Log ===" > "$log_file"
echo "Start time: $(date +'%Y-%m-%d %H:%M:%S')" >> "$log_file"
echo "----------------------------------------" >> "$log_file"
else
echo "----------------------------------------" >> "$log_file"
echo "Test resumed at: $(date +'%Y-%m-%d %H:%M:%S')" >> "$log_file"
fi
# 启动后等待30秒,确保系统初始化完成
echo "System starting, wait 30s for stability..." >> "$log_file"
sleep 30
# 主循环:周期性检查并执行逻辑
while true; do
current_time=$(date +'%Y-%m-%d %H:%M:%S')
echo -e "\n[Check at $current_time]" >> "$log_file" # 标记当前检查时间
# 检查TF卡是否正确挂载(设备节点+挂载路径双重验证)
if mount | grep -q "$device"p1 && mount | grep -q "$mount_point"; then
# 分支1:TF卡挂载正常 → 执行重启
echo "TF card status: Mounted normally (device: $device p1, path: $mount_point)" >> "$log_file"
echo "Executing MPU reboot..." >> "$log_file"
# 执行重启命令并记录结果
if $reset_cmd; then
echo "Reboot command executed successfully" >> "$log_file"
else
echo "Warning: Reboot command failed (check if $reset_cmd is valid)" >> "$log_file"
fi
# 等待MPU重启并稳定
echo "Waiting $reboot_wait seconds for MPU to reboot..." >> "$log_file"
sleep $reboot_wait
else
# 分支2:TF卡未挂载 → 不重启,记录错误信息
echo "TF card status: NOT mounted (expected device: $device p1, path: $mount_point)" >> "$log_file"
# 抓取dmesg中与TF卡相关的日志
echo "Related dmesg logs (last 30 lines):" >> "$log_file"
dmesg -T | tail -n 30 | grep -E "mmc|sdcard|mount|$device" >> "$log_file" # 筛选关键日志
echo "No related logs" >> "$log_file" # 若上述命令无输出,显示此提示
fi
# 等待到下一个周期
echo -e "\nWait $cycle_wait seconds for next check..." >> "$log_file"
sleep $cycle_wait
done
长时间挂载测试后,发现拔掉SD卡,报错,内核重复打印

重新带电插上也不不可恢复,插拔了几下SD卡, 负载明显上升

此时读取TF 卡GPIO状态,发现该值为断电状态

重新echo 0 > /sys/class/gpio/gpio308/value后正常,TF重新扫卡

我的脚本中很明显有改引脚的复位,为什么该值会为1呢???
更新脚本,在扫卡初始化之前进行判断,若值为1先进行置0
在该脚本中加一个函数,用于检查gpio308的值,如果该值是1的写入0, echo 0 > /sys/class/gpio/gpio308/value,然后在reset_tf_card和echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert中间调用此函数
#!/bin/bash
MOUNT_POINT="/mnt/sdcard/mmcblk1p1"
PARTITION="/dev/mmcblk1p1"
DEVICE="/dev/mmcblk1"
CHECK_INTERVAL=20
RESET_DELAY=5
DETECT_PIN_STATUS="/sys/class/mmc_host/mmc1/device/mmc_host/mmc1/device/sunxi_sd_detect_pin_status"
check_and_fix_gpio308() {
# 检查GPIO308是否已导出
if [ ! -d "/sys/class/gpio/gpio308" ]; then
echo "GPIO308未导出,正在导出..."
echo 308 > /sys/class/gpio/export 2>/dev/null
sleep 0.1
# 检查是否成功导出
if [ ! -d "/sys/class/gpio/gpio308" ]; then
echo "错误: 无法导出GPIO308"
return 1
fi
# 设置方向为输出
echo out > /sys/class/gpio/gpio308/direction 2>/dev/null
echo "已导出并设置GPIO308方向为输出"
fi
# 检查当前值并修复
local gpio_value=$(cat /sys/class/gpio/gpio308/value 2>/dev/null)
if [ "$gpio_value" = "1" ]; then
echo 0 > /sys/class/gpio/gpio308/value
else
echo "GPIO308状态正常: $gpio_value"
fi
return 0
}
# 执行复位操作
reset_tf_card() {
echo "执行TF卡复位操作..."
echo 308 > /sys/class/gpio/export 2>/dev/null
# 等待GPIO就绪
sleep 1
echo out > /sys/class/gpio/gpio308/direction
echo 1 > /sys/class/gpio/gpio308/value
echo "当前磁盘挂载情况:"
df -h
sleep 1
echo 0 > /sys/class/gpio/gpio308/value
# 取消导出GPIO
echo 308 > /sys/class/gpio/unexport 2>/dev/null
echo "TF卡复位完成"
}
while true; do
current_time=$(date '+%H:%M:%S')
reset_performed=false
# 检查TF卡设备是否存在 - 通过检测引脚状态
pin_status=$(cat "$DETECT_PIN_STATUS" 2>/dev/null)
if [ "$pin_status" = "0" ]; then
echo "$current_time: 未插入TF卡设备"
sleep $CHECK_INTERVAL
continue
fi
# 使用df -T检查挂载状态和文件系统格式
mount_info=$(df -T "$MOUNT_POINT" 2>/dev/null | grep "$PARTITION")
# 检查条件
if [ -z "$mount_info" ]; then
echo "$current_time: 未挂载 - 执行重置"
reset_tf_card
check_and_fix_gpio308
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif [ "$(echo "$mount_info" | awk '{print $2}')" != "ext4" ]; then
echo "$current_time: 文件系统非ext4 - 执行重置"
reset_tf_card
check_and_fix_gpio308
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif mount | grep "$MOUNT_POINT" | grep -q "ro,"; then
echo "$current_time: 只读模式 - 执行重置"
reset_tf_card
check_and_fix_gpio308
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif ! touch "$MOUNT_POINT/.test" 2>/dev/null; then
echo "$current_time: 无法写入 - 执行重置"
reset_tf_card
check_and_fix_gpio308
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
elif dmesg | tail -30 | grep -i "$DEVICE" | grep -i "error\|I/O\|fail\|bad" >/dev/null; then
echo "$current_time: 检测到存储错误 - 执行重置"
reset_tf_card
check_and_fix_gpio308
echo 0 > /sys/class/mmc_host/mmc1/device/sunxi_insert
reset_performed=true
else
echo "$current_time: 正常"
rm -f "$MOUNT_POINT/.test" 2>/dev/null
fi
# 等待挂载
if [ "$reset_performed" = true ]; then
echo "等待${RESET_DELAY}秒让TF卡重新初始化..."
sleep $RESET_DELAY
else
sleep $CHECK_INTERVAL
fi
done
测试后正常!!!