TF卡---热插拔

一、热插拔

1.主题

热插拔TF卡时如何自动挂载到指定目录

2.问题背景

功能需求:产品正常使用过程中,热插拔TF卡,除了实时更新/dev下的设备节点,还要自动挂载文件系统到/mnt/sdcard/,和自动卸载/mnt/sdcard/,达到插上能直接用的效果。

达到的效果:插上tf卡,df命令有/dev/mmcblk1p1 120934096 8523164 106221672 7% /mnt/sdcard/mmcblk1p1拔出tf卡后消失。

3.问题分析

首先要实现自动挂载功能,先要确认你当前使用的热插拔方式。

热插拔主流有两种方式:

方式一:udev。

方式二:devtmpfs。

区别点:

  1. 方式二是内核里同步创建设备节点,方式一的是异步。所以devtmpfs要比udev方式更实时。

  2. 方式一需要在用户空间配合mdev或udevd工具,达到刷新设备节点的效果。方式二不需要。

  3. 方式一默认采用tmpfs文件系统,方式二是devtmpfs文件系统。

  4. 方式一可以用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

测试后正常!!!

相关推荐
企鹅侠客1 小时前
Linux性能调优 详解磁盘工作流程及性能指标
linux·运维·服务器·性能调优
企鹅侠客1 小时前
Linux性能调优 再谈磁盘性能指标和进程级IO
linux·运维·服务器·性能调优
wdfk_prog1 小时前
[Linux]学习笔记系列 -- [block][mq-deadline]
linux·笔记·学习
不过普通话一乙不改名2 小时前
Linux 网络收包的进阶之路:从普通 socket 到 AF_XDP 零拷贝
linux·运维·网络
Zeku2 小时前
20251125 - 韦东山Linux第三篇笔记【中】
linux·驱动开发
在路上@Amos2 小时前
Linux 命令行查看 串口hex数据
linux·运维·服务器
人工智能训练2 小时前
Linux 系统核心快捷键表(可打印版)
linux·运维·服务器·人工智能·ubuntu·容器·openeuler
大聪明-PLUS2 小时前
C++ 中的引用和引用类型
linux·嵌入式·arm·smarc