一、miscdevice

只有基礎設備節點,無 /sys/class/misc/myled/ 詳細屬性

// misc_register() 內部實現
int misc_register(struct miscdevice *misc)
{
    // ... 省略錯誤檢查
    dev = MKDEV(MISC_MAJOR, misc->minor);  // 強制主設備號10
    cdev_init(&misc->cdev, misc->fops);    // 內部使用cdev
    cdev_add(&misc->cdev, dev, 1);
    device_create(misc_class, NULL, dev, misc, "%s", misc->name);
}
miscdevice 本質上是預配置好設備號、類、節點名稱的  cdev 快捷方式  。

二、cdev + class

  • 自動生成完整的 /sys/class/myled_class/myled/ 目錄,支持 udev 規則
static int __init my_init(void)
{
    // 1. 分配設備號
    alloc_chrdev_region(&dev_num, 0, 1, "myled");
    
    // 2. 初始化cdev
    cdev_init(&my_cdev, &led_fops);
    cdev_add(&my_cdev, dev_num, 1);
    
    // 3. 創建sysfs類
    my_class = class_create(THIS_MODULE, "myled_class");
    
    // 4. 創建設備節點
    device_create(my_class, NULL, dev_num, NULL, "myled");
    
    return 0;
}
1. 舊式簡化API(已不推薦使用)
// 一句話完成:註冊主設備號 + cdev_init/add
// 缺點:無法動態次設備號、不靈活、隱藏細節
register_chrdev(major, "name", &fops);

linux下的USB驅動開發_#驅動開發

linux下的USB驅動開發_字符設備_02

  1. Platform_Driver 
  • 私有數據管理方法1:
// 你的私有數據結構
struct my_private_data {
    struct gpio_desc *led;
    int value;
};

static int example_probe(struct platform_device *pdev)
{
    // 在probe中賦值
    pdev->dev.driver_data = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    // 問題1:無類型檢查,任何void*都能賦值
    // 問題2:在remove中使用時:
    struct my_private_data *priv = pdev->dev.driver_data;  // 隱式轉換,無警告
}
  • 方私有數據管理方法2(好):
// 在probe中:
static int example_probe(struct platform_device *pdev)
{

    struct my_private_data *priv = devm_kzalloc(...);
    platform_set_drvdata(pdev, priv);  // 宏內部有類型檢查
}
static int example_remove(struct platform_device *pdev)
{
    // 在remove中:
    struct my_private_data *priv = platform_get_drvdata(pdev);  // 返回正確類型
}


關鍵實現(include/linux/platform_device.h):

static inline void *platform_get_drvdata(const struct platform_device *pdev)
{
    return dev_get_drvdata(&pdev->dev);
}

static inline void platform_set_drvdata(struct platform_device *pdev, void *data)
{
    dev_set_drvdata(&pdev->dev, data);
}

早期設備樹(Device Tree)

某些驅動讓設備樹或ACPI負責設備宣告:

// 只需註冊cdev,設備文件由dt-overlay描述
// 內核自動解析並創建,無需手動class_create

linux下的USB驅動開發_#linux_03

linux下的USB驅動開發_#驅動開發_04

三、設備樹

生成plateform_device

設備樹生成 platform_device 是 Linux 內核啓動時的核心流程,由 drivers/of/platform.c 驅動完成。以下是完整機制解析:

在內核啓動早期,arch/arm/mach-xxx/ 或通用代碼調用:

// arch/arm/mach-imx/mach-imx8m.c
void __init imx8m_init_machine(void)
{
    of_platform_populate(NULL, NULL, NULL, NULL);  // ← 關鍵函數
}

該函數啓動對整個設備樹的遞歸掃描,將符合條件的節點轉換為 platform_device

在 Linux 內核中,platform_device 註冊後在 sysfs 中生成的是文件夾(目錄),而不是文件。

## Platform_device 設備與字符設備的關係

如果給 Platform_device添加ATTR

具體的方法:

在platreform_driver_xxx.c 中添加 

// 顯示當前 DMA 狀態
static ssize_t dma_mode_show(struct device *dev,
                             struct device_attribute *attr, char *buf)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct example_driver_data *drv_data = platform_get_drvdata(pdev);
    
    if (!drv_data) {
        return -ENODEV;
    }
    
    // 返回當前 DMA 模式的字符串表示
    switch (drv_data->dma_mode) {
    case 0:
        return sprintf(buf, "disabled\n");
    case 1:
        return sprintf(buf, "direct\n");
    case 2:
        return sprintf(buf, "scatter-gather\n");
    default:
        return sprintf(buf, "unknown (%d)\n", drv_data->dma_mode);
    }
}

// 動態設置 DMA 狀態
static ssize_t dma_mode_store(struct device *dev,
                              struct device_attribute *attr,
                              const char *buf, size_t count)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct example_driver_data *drv_data = platform_get_drvdata(pdev);
    int mode;
    int ret;
    
    if (!drv_data) {
        return -ENODEV;
    }
    
    // 從用户空間讀取模式值
    ret = kstrtoint(buf, 10, &mode);
    if (ret < 0) {
        // 嘗試解析字符串形式的模式
        if (strncmp(buf, "disabled", 8) == 0) {
            mode = 0;
        } else if (strncmp(buf, "direct", 6) == 0) {
            mode = 1;
        } else if (strncmp(buf, "scatter-gather", 14) == 0) {
            mode = 2;
        } else {
            return -EINVAL; // 無效的模式值
        }
    }
    
    // 驗證模式值是否有效
    if (mode < 0 || mode > 2) {
        return -EINVAL;
    }
    
    // 設置新的 DMA 模式
    drv_data->dma_mode = mode;
    printk(KERN_INFO "Example platform driver: DMA mode set to %d\n", mode);
    
    return count;
}

// 定義設備屬性
static DEVICE_ATTR_RW(dma_mode);
// DEVICE_ATTR_RW 宏展開後自動創建:
// - dma_mode 屬性
// - .show = dma_mode_show (讀回調)
// - .store = dma_mode_store (寫回調)

在probe 中添加

static int example_probe(struct platform_device *pdev)
{



     ---------其它代碼省略----------
  




   /* 創建 sysfs 屬性文件 - 在 platform 設備目錄下 */
    ret = device_create_file(&pdev->dev, &dev_attr_dma_mode);
    if (ret < 0) {
        printk(KERN_ERR "Failed to create sysfs attribute: %d\n", ret);
        return ret;
    }
}

在這個probe代碼中創建了字符設備,接着註冊了sysfs 文件屬性,我們目前的實現在 platform 設備下創建了 dma_mode 屬性文件,但這確實引發了一個關於設備屬性歸屬的問題。讓我來解釋一下:
在 Linux 驅動模型中,這是兩個不同但相關的概念:

1. Platform 設備 :代表底層硬件抽象,是硬件與驅動程序之間的接口
2. 字符設備 :是提供給用户空間程序訪問的設備節點

## 應該如何處理?
這取決於您的設計意圖:

1. 如果 dma_mode 是硬件配置參數 :
   
   - 當前實現在 platform 設備下創建是合理的,因為它代表硬件配置
   - 這使得屬性文件出現在 /sys/devices/platform/example-pdrv/ 目錄下
2. 如果 dma_mode 是字符設備的運行時配置 :
   
   - 應該在字符設備上創建設備屬性
   - 這樣屬性文件會出現在 /sys/class/example-driver/example-driver/ 目錄下

# 設備級屬性(platform_device)
/sys/devices/platform/soc/4000f000.serial/dma_mode
# ↑修改隻影響USART3

# 類級屬性(class)
/sys/class/stm32-uart/global_log_level
# ↑修改影響所有STM32 UART設備

四、轉換流程(四步過濾)

Step 1:設備樹節點解析

// drivers/of/platform.c
static int of_platform_bus_create(...)
{
    struct device_node *child;
    // 遍歷所有子節點
    for_each_child_of_node(bus, child) {
        of_platform_device_create_pdata(child, ...);
    }
}

Step 2:四重過濾判斷

內核嚴格檢查節點屬性,任一條件不滿足則跳過

static struct platform_device *of_platform_device_create_pdata(...)
{
    struct device_node *np = pdev->dev.of_node;
    
    // ① 必須有 compatible 屬性
    if (!of_get_property(np, "compatible", NULL))
        return NULL;  // ❌ 無compatible,不是設備
    
    // ② status 必須是 "ok" 或 "okay"
    if (!of_device_is_available(np))
        return NULL;  // ❌ status = "disabled"
    
    // ③ 不能有 "interrupt-parent" 且父節點是根節點(特殊情況)
    // ④ 必須匹配父級的 #address-cells 和 #size-cells(總線設備)
    
    // ✅ 通過所有檢查,創建 platform_device
    return of_device_alloc(np, bus_id, parent);
}

Step 3:創建 platform_device 實例

通過檢查的設備節點被轉換為內核結構體:

struct platform_device *of_device_alloc(...)
{
    struct platform_device *pdev;
    
    // 1. 分配 platform_device 結構體
    pdev = platform_device_alloc("", PLATFORM_DEVID_NONE);
    
    // 2. 關聯 device_node(保留設備樹信息)
    pdev->dev.of_node = of_node_get(np);
    
    // 3. 解析 reg 屬性,填充 resource(IO內存/中斷)
    of_address_to_resource(np, 0, &res);  // 解析reg為IO內存資源
    of_irq_to_resource(np, 0, &res);      // 解析interrupts為中斷資源
    
    // 4. 註冊到 platform 總線
    platform_device_add(pdev);
    
    return pdev;
}

Step 4:與 platform_driver 匹配

生成的 platform_device 等待匹配的驅動:

// drivers/base/platform.c
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);
    
    // 1. 優先匹配 of_node 的 compatible
    of_driver_match_device(dev, drv);
    // 對比 pdev->dev.of_node->compatible 和 pdrv->driver->of_match_table
    
    // 2. 備選:匹配 platform_device_id
    // 3. 備選:匹配設備名
}

5:設備樹和plateform_device 對應關係

1. 設備樹節點(dts源碼)

usart3: serial@40004800 {
    compatible = "st,stm32-usart", "st,stm32-uart";  // ①
    reg = <0x40004800 0x400>;                        // ②
    interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;  // ③
    clocks = <&rcc 0 STM32_AHB1_CLOCK(RCC_AHB1ENR_USART3EN)>; // ④
    status = "okay";                                 // ⑤
};

2. 內核生成的 platform_device 結構體

// 內核自動填充(drivers/of/platform.c 中創建)
struct platform_device {
    .name = "40004800.serial",          // 來自節點名 serial@40004800
    .id = PLATFORM_DEVID_NONE,          // 無ID
    .dev = {
        .of_node = {                    // 保留完整的設備樹節點
            .name = "serial",
            .full_name = "/soc/serial@40004800",
            .properties = {
                { .name = "compatible", .value = "st,stm32-usart\0st,stm32-uart\0" },
                { .name = "reg", .value = <0x40004800 0x400> },
                { .name = "interrupts", .value = <...> },
                { .name = "clocks", .value = <...> },
                { .name = "status", .value = "okay\0" },
            }
        },
        .platform_data = NULL,          // 通常不用,驅動直接從of_node解析
        .dma_mask = &some_mask,
        .coherent_dma_mask = 0xffffffff,
    },
    .resource = {                        // 自動解析 reg 和 interrupts 生成
        [0] = {                          // reg 解析為 IORESOURCE_MEM
            .start = 0x40004800,
            .end   = 0x40004800 + 0x400 - 1,
            .flags = IORESOURCE_MEM,
            .name  = NULL,               // 使用節點名
        },
        [1] = {                          // interrupts 解析為 IORESOURCE_IRQ
            .start = 39,                 // IRQ號
            .end   = 39,
            .flags = IORESOURCE_IRQ | IRQF_TRIGGER_HIGH,
            .name  = NULL,
        }
    },
    .num_resources = 2,                 // 2個資源項(MEM + IRQ)
};

3. 生成的 sysfs 文件系統路徑

# 設備註冊後,出現在 /sys/devices 和 /sys/bus/platform
$ ls -l /sys/devices/platform/soc/40004800.serial/
drwxr-xr-x  4 root root    0 Jan  1 00:00 power/
-rw-r--r--  1 root root 4096 Jan  1 00:00 driver_override
-r--r--r--  1 root root 4096 Jan  1 00:00 modalias
lrwxrwxrwx  1 root root    0 Jan  1 00:00 driver -> ../../../bus/platform/drivers/stm32-usart
lrwxrwxrwx  1 root root    0 Jan  1 00:00 of_node -> ../../../firmware/devicetree/base/soc/serial@40004800
-r--r--r--  1 root root 4096 Jan  1 00:00 resource0  # 對應 reg 的內存映射
-r--r--r--  1 root root 4096 Jan  1 00:00 resource1  # 對應 interrupts 的IRQ號

# 驅動匹配後,生成 /sys/class/tty/ 下的設備節點
$ ls -l /sys/class/tty/ttySTM3
lrwxrwxrwx  1 root root 0 Jan  1 00:00 /sys/class/tty/ttySTM3 -> ../../devices/platform/soc/40004800.serial/tty/ttySTM3

4. 驅動匹配(platform_driver)

// drivers/tty/serial/stm32-usart.c
static const struct of_device_id stm32_match[] = {
    { .compatible = "st,stm32-usart" },  // ① 匹配設備樹 compatible
    { .compatible = "st,stm32-uart" },
    {}
};
MODULE_DEVICE_TABLE(of, stm32_match);

static struct platform_driver stm32_usart_driver = {
    .probe = stm32_usart_probe,          // ② 匹配成功後調用
    .remove = stm32_usart_remove,
    .driver = {
        .name = "stm32-usart",
        .of_match_table = stm32_match,  // ③ 綁定匹配表
    },
};

// 匹配成功後,內核自動調用
static int stm32_usart_probe(struct platform_device *pdev)
{
    // ④ 從 platform_device 提取資源
    struct resource *res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    struct resource *res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    
    // ⑤ 從 of_node 提取時鐘、DMA等複雜屬性
    struct device_node *np = pdev->dev.of_node;
    clk = of_clk_get(np, 0);
    
    // ⑥ 請求內存區域
    request_mem_region(res_mem->start, resource_size(res_mem), "stm32-usart");
    
    // ⑦ ioremap 映射到內核虛擬地址
    base = ioremap(res_mem->start, resource_size(res_mem));
    
    // ⑧ 註冊中斷
    request_irq(res_irq->start, stm32_irq, IRQF_TRIGGER_HIGH, "stm32-usart", port);
    
    // ⑨ 註冊 tty 設備
    uart_add_one_port(&stm32_uart_driver, &stm32_port->port);
    
    return 0;
}

5. 用户空間最終看到的設備

# 字符設備節點(由 tty 子系統創建)
$ ls -l /dev/ttySTM3
crw-rw---- 1 root dialout 250, 0 Jan  1 00:00 /dev/ttySTM3

# 設備樹原始信息
$ ls -l /sys/firmware/devicetree/base/soc/serial@40004800/
-r--r--r-- 1 root root  8 Jan  1 00:00 name
-r--r--r-- 1 root root 24 Jan  1 00:00 compatible
-r--r--r-- 1 root root  8 Jan  1 00:00 reg
-r--r--r-- 1 root root  4 Jan  1 00:00 interrupts
-r--r--r-- 1 root root  4 Jan  1 00:00 clocks

五、plateform_device 和plateform_driver 

Makefile

#Makefile

obj-m += platform_device_example.o
obj-m += platform_driver_example.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

 platform_device_example.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/err.h>

/* 使用0避免與系統資源衝突 */
#define EXAMPLE_DEV_MEM_BASE    0x0  /* 使用0避免衝突 */
#define EXAMPLE_DEV_MEM_SIZE    0x1000
#define EXAMPLE_DEV_IRQ         0    /* 使用0表示無真實中斷 */

/* 定義設備資源 - 在Ubuntu測試環境中使用虛擬資源 */
static struct resource example_resources[] = {
    /* 註釋掉可能導致衝突的資源定義 */
    /*
    [0] = {
        .start  = EXAMPLE_DEV_MEM_BASE,
        .end    = EXAMPLE_DEV_MEM_BASE + EXAMPLE_DEV_MEM_SIZE - 1,
        .flags  = IORESOURCE_MEM,
    },
    [1] = {
        .start  = EXAMPLE_DEV_IRQ,
        .end    = EXAMPLE_DEV_IRQ,
        .flags  = IORESOURCE_IRQ,
    },
    */
};

/* 設備私有數據 */
static struct example_dev_data {
    int version;
    char name[32];
} example_data = {
    .version = 1,
    .name = "example-platform-device",
};

/* 定義platform_device結構體 */
/* 設備釋放函數 - 用於正確管理設備的生命週期 */
static void example_dev_release(struct device *dev)
{
    printk(KERN_INFO "Example platform device: release function called\n");
}

static struct platform_device example_platform_device = {
    .name   = "example-pdrv", /* 必須與platform_driver的name匹配 */
    .id     = -1,                         /* 單一設備 */
    .num_resources  = 0,                  /* 0個資源,避免衝突 */
    .resource       = example_resources,
    .dev = {
        .platform_data = &example_data,   /* 傳遞給驅動的私有數據 */
        .release = example_dev_release,   /* 設置release函數 */
    },
};

static int __init example_device_init(void)
{
    int ret;
    
    printk(KERN_INFO "Example platform device: registering\n");
    
    /* 註冊platform_device */
    ret = platform_device_register(&example_platform_device);
    if (ret) {
        printk(KERN_ERR "Failed to register platform device: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "Example platform device: registered successfully\n");
    return 0;
}

static void __exit example_device_exit(void)
{
    printk(KERN_INFO "Example platform device: unregistering\n");
    
    /* 註銷platform_device */
    platform_device_unregister(&example_platform_device);
    
    printk(KERN_INFO "Example platform device: unregistered successfully\n");
}

module_init(example_device_init);
module_exit(example_device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("Platform Device Example");
MODULE_VERSION("1.0");

 platform_driver_example.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
//#define  platform_device_attr 1


/* 設備私有數據結構體 - 與platform_device_example.c中的定義保持一致 */
struct example_dev_data {
    int version;
    char name[32];
};

/* 驅動私有數據結構 */
struct example_driver_data {
    void __iomem *base_addr;  /* 映射後的內存地址 */
    int irq;                  /* 中斷號 */
    struct resource *mem_res; /* 內存資源 */
    struct resource *irq_res; /* 中斷資源 */
    dev_t dev_num;            /* 設備號 */
    struct cdev cdev;         /* 字符設備結構體 */
    struct class *class;      /* 設備類 */
    struct device *device;    /* 設備 */
    char data[256];           /* 用於演示的數據緩衝區 */
    loff_t data_len;          /* 數據長度 - 與pos類型保持一致 */
    int dma_mode;             /* DMA 工作模式:0-關閉,1-直接模式,2-分散/聚集模式 */
};

/* 設備樹匹配表 - 用於支持設備樹 */
static const struct of_device_id example_of_match[] = {
    { .compatible = "example,example-pdrv", },
    {},
};
MODULE_DEVICE_TABLE(of, example_of_match);

/* ID表 - 用於傳統匹配方式 */
static const struct platform_device_id example_id_table[] = {
    { "example-pdrv", 0 },
    {},
};
MODULE_DEVICE_TABLE(platform, example_id_table);

/* 文件操作函數聲明 */
static int example_open(struct inode *inode, struct file *file);
static int example_release(struct inode *inode, struct file *file);
static ssize_t example_read(struct file *file, char __user *buf, size_t count, loff_t *pos);
static ssize_t example_write(struct file *file, const char __user *buf, size_t count, loff_t *pos);

/* 字符設備初始化函數 */
static int example_setup_char_device(struct example_driver_data *drv_data);
/* 字符設備資源清理函數 */
static void example_cleanup_char_device(struct example_driver_data *drv_data);

/* probe函數 - 當設備和驅動匹配成功時調用 */

/* 文件操作結構體定義 */
static struct file_operations example_fops = {
    .owner = THIS_MODULE,
    .open = example_open,
    .release = example_release,
    .read = example_read,
    .write = example_write,
};

static int example_open(struct inode *inode, struct file *file)
{
    struct example_driver_data *drv_data;
    
    drv_data = container_of(inode->i_cdev, struct example_driver_data, cdev);
    file->private_data = drv_data;
    
    printk(KERN_INFO "Example driver: device opened\n");
    return 0;
}

static int example_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Example driver: device released\n");
    return 0;
}

static ssize_t example_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    struct example_driver_data *drv_data = file->private_data;
    size_t read_size;
    loff_t available_bytes;
    
    if (*pos >= drv_data->data_len) {
        return 0; /* 已到達文件末尾 */
    }
    
    /* 計算可用字節數 */
    available_bytes = drv_data->data_len - *pos;
    
    /* 避免使用min()函數比較不同類型,完全使用條件判斷 */
    if (available_bytes > (loff_t)count) {
        read_size = count;
    } else {
        /* 直接轉換,確保類型安全 */
        read_size = (size_t)available_bytes;
    }
    
    /* 額外的安全檢查 */
    if (read_size > count) {
        read_size = count;
    }
    
    if (copy_to_user(buf, drv_data->data + *pos, read_size)) {
        return -EFAULT;
    }
    
    *pos += read_size;
    printk(KERN_INFO "Example driver: read %zu bytes\n", read_size);
    
    return read_size;
}

static ssize_t example_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
    struct example_driver_data *drv_data = file->private_data;
    size_t write_size;
    
    if (*pos >= sizeof(drv_data->data)) {
        return -ENOSPC; /* 緩衝區已滿 */
    }
    
    write_size = min(count, sizeof(drv_data->data) - *pos);
    
    if (copy_from_user(drv_data->data + *pos, buf, write_size)) {
        return -EFAULT;
    }
    
    *pos += write_size;
    /* 避免使用max()函數比較不同類型的值 */
    if (*pos > drv_data->data_len) {
        drv_data->data_len = *pos;
    }
    printk(KERN_INFO "Example driver: written %zu bytes\n", write_size);
    
    return write_size;
}

// 顯示當前 DMA 狀態
static ssize_t dma_mode_show(struct device *dev,
                             struct device_attribute *attr, char *buf)
{
    #ifdef platform_device_attr
    //將dma_mode屬性文件創建在platform設備上
    struct platform_device *pdev = to_platform_device(dev);
    struct example_driver_data *drv_data = platform_get_drvdata(pdev);
    
    #else
    //將dma_mode屬性文件創建在字符設備上
    struct example_driver_data *drv_data = dev_get_drvdata(dev);
    #endif
    if (!drv_data) {
        return -ENODEV;
    }
    
    // 返回當前 DMA 模式的字符串表示
    switch (drv_data->dma_mode) {
    case 0:
        return sprintf(buf, "disabled\n");
    case 1:
        return sprintf(buf, "direct\n");
    case 2:
        return sprintf(buf, "scatter-gather\n");
    default:
        return sprintf(buf, "unknown (%d)\n", drv_data->dma_mode);
    }
}

// 動態設置 DMA 狀態
static ssize_t dma_mode_store(struct device *dev,
                              struct device_attribute *attr,
                              const char *buf, size_t count)
{
    #ifdef platform_device_attr
    //將dma_mode屬性文件創建在platform設備上
    struct platform_device *pdev = to_platform_device(dev);
    struct example_driver_data *drv_data = platform_get_drvdata(pdev);
    #else
     //將dma_mode屬性文件創建在字符設備上
    struct example_driver_data *drv_data = dev_get_drvdata(dev);
    #endif
    int mode;
    int ret;
    
    if (!drv_data) {
        return -ENODEV;
    }
    
    // 從用户空間讀取模式值
    ret = kstrtoint(buf, 10, &mode);
    if (ret < 0) {
        // 嘗試解析字符串形式的模式
        if (strncmp(buf, "disabled", 8) == 0) {
            mode = 0;
        } else if (strncmp(buf, "direct", 6) == 0) {
            mode = 1;
        } else if (strncmp(buf, "scatter-gather", 14) == 0) {
            mode = 2;
        } else {
            return -EINVAL; // 無效的模式值
        }
    }
    
    // 驗證模式值是否有效
    if (mode < 0 || mode > 2) {
        return -EINVAL;
    }
    
    // 設置新的 DMA 模式
    drv_data->dma_mode = mode;
    printk(KERN_INFO "Example platform driver: DMA mode set to %d\n", mode);
    
    return count;
}

// 定義設備屬性
static DEVICE_ATTR_RW(dma_mode);
// DEVICE_ATTR_RW 宏展開後自動創建:
// - dma_mode 屬性
// - .show = dma_mode_show (讀回調)
// - .store = dma_mode_store (寫回調)


static int example_probe(struct platform_device *pdev)
{
    struct example_driver_data *drv_data;
    struct example_dev_data *dev_data;
    int ret = 0;
    
    printk(KERN_INFO "Example platform driver: probe function called\n");
    
    /* 分配驅動私有數據 */
    drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL);
    if (!drv_data) {
        return -ENOMEM;
    }
    
    /* 獲取設備私有數據 */
    dev_data = pdev->dev.platform_data;
    if (dev_data) {
        printk(KERN_INFO "Example platform driver: device version %d, name %s\n",
               dev_data->version, dev_data->name);
    }
    
    /* 初始化數據緩衝區 */
    strcpy(drv_data->data, "Example Driver Initial Data\n");
    drv_data->data_len = (loff_t)strlen(drv_data->data);
    
    /* 初始化 DMA 模式為禁用狀態 */
    drv_data->dma_mode = 0;
    
    /* 設置字符設備 - 調用獨立的函數處理字符設備註冊 */
    ret = example_setup_char_device(drv_data);
    if (ret < 0) {
        printk(KERN_ERR "Failed to setup char device: %d\n", ret);
        return ret;
    }
    
    /* 保存驅動數據到設備中 */
    platform_set_drvdata(pdev, drv_data);
    
    #ifdef platform_device_attr
    /* 創建 sysfs 屬性文件 - 在 platform 設備目錄下 */
    ret = device_create_file(&pdev->dev, &dev_attr_dma_mode);
    if (ret < 0) {
        printk(KERN_ERR "Failed to create sysfs attribute: %d\n", ret);
        return ret;
    }
    #else

    /* 同時保存驅動數據到字符設備中,供sysfs屬性使用 */
    dev_set_drvdata(drv_data->device, drv_data);
    
    /* 創建 sysfs 屬性文件 - 在字符設備目錄下 */
    ret = device_create_file(drv_data->device, &dev_attr_dma_mode);
    if (ret < 0) {
        printk(KERN_ERR "Failed to create sysfs attribute: %d\n", ret);
        return ret;
    }
    #endif

    printk(KERN_INFO "Example platform driver: probe successful, device node created\n");
    return 0;
}

/* 字符設備初始化函數 */
static int example_setup_char_device(struct example_driver_data *drv_data)
{
    int ret = 0;
    
    /* 註冊字符設備 */
    ret = alloc_chrdev_region(&drv_data->dev_num, 0, 1, "example-driver");
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate char device region\n");
        return ret;
    }
    
    /* 初始化並添加cdev */
    cdev_init(&drv_data->cdev, &example_fops);
    drv_data->cdev.owner = THIS_MODULE;
    
    ret = cdev_add(&drv_data->cdev, drv_data->dev_num, 1);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add char device\n");
        unregister_chrdev_region(drv_data->dev_num, 1);
        return ret;
    }
    
    /* 創建設備類 */
    drv_data->class = class_create(THIS_MODULE, "example-driver");
    if (IS_ERR(drv_data->class)) {
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(&drv_data->cdev);
        unregister_chrdev_region(drv_data->dev_num, 1);
        return PTR_ERR(drv_data->class);
    }
    
    /* 創建設備節點 */
    drv_data->device = device_create(drv_data->class, NULL, drv_data->dev_num, NULL, "example-driver");
    if (IS_ERR(drv_data->device)) {
        printk(KERN_ERR "Failed to create device\n");
        class_destroy(drv_data->class);
        cdev_del(&drv_data->cdev);
        unregister_chrdev_region(drv_data->dev_num, 1);
        return PTR_ERR(drv_data->device);
    }
    
    return 0;
}
 

static int example_setup_char_device_parent(struct example_driver_data *drv_data, struct platform_device *pdev)
{
    int ret = 0;
    
    /* 註冊字符設備 */
    ret = alloc_chrdev_region(&drv_data->dev_num, 0, 1, "example-driver");
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate char device region\n");
        return ret;
    }
    
    /* 初始化並添加cdev */
    cdev_init(&drv_data->cdev, &example_fops);
    drv_data->cdev.owner = THIS_MODULE;
    
    ret = cdev_add(&drv_data->cdev, drv_data->dev_num, 1);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add char device\n");
        unregister_chrdev_region(drv_data->dev_num, 1);
        return ret;
    }
    
    /* 創建設備類 */
    drv_data->class = class_create(THIS_MODULE, "example-driver");
    if (IS_ERR(drv_data->class)) {
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(&drv_data->cdev);
        unregister_chrdev_region(drv_data->dev_num, 1);
        return PTR_ERR(drv_data->class);
    }
    
    /* 創建設備節點 - 使用platform設備作為父設備 */
    drv_data->device = device_create(drv_data->class, &pdev->dev, drv_data->dev_num, NULL, "example-driver");
    if (IS_ERR(drv_data->device)) {
        printk(KERN_ERR "Failed to create device\n");
        class_destroy(drv_data->class);
        cdev_del(&drv_data->cdev);
        unregister_chrdev_region(drv_data->dev_num, 1);
        return PTR_ERR(drv_data->device);
    }
    
    /* 在sysfs中創建從字符設備到platform設備的鏈接,方便識別關聯關係 */
    ret = sysfs_create_link(&drv_data->device->kobj, &pdev->dev.kobj, "device");
    if (ret) {
        printk(KERN_WARNING "Failed to create sysfs link to platform device\n");
        /* 鏈接創建失敗不影響主要功能,繼續執行 */
    }
    
    return 0;
}




/* remove函數 - 當設備被移除時調用 */
static int example_remove(struct platform_device *pdev)
{
    struct example_driver_data *drv_data = platform_get_drvdata(pdev);
    
    printk(KERN_INFO "Example platform driver: remove function called\n");
    
    #ifdef platform_device_attr
    /* 移除 sysfs 屬性文件 */
    device_remove_file(&pdev->dev, &dev_attr_dma_mode);
    #else
    /* 移除 sysfs 屬性文件 */
    device_remove_file(drv_data->device, &dev_attr_dma_mode);
    #endif
    
    /* 清理字符設備相關資源 - 調用獨立的清理函數 */
    if (drv_data) {
        example_cleanup_char_device(drv_data);
    }
    
    printk(KERN_INFO "Example platform driver: remove successful\n");
    return 0;
}

/* 字符設備資源清理函數 */
static void example_cleanup_char_device(struct example_driver_data *drv_data)
{
    if (drv_data->device && !IS_ERR(drv_data->device)) {
        /* 只銷毀設備,不清理設備屬性文件(設備屬性在example_remove中已處理) */
        device_destroy(drv_data->class, drv_data->dev_num);
    }
    
    if (drv_data->class && !IS_ERR(drv_data->class)) {
        class_destroy(drv_data->class);
    }
    
    cdev_del(&drv_data->cdev);
    unregister_chrdev_region(drv_data->dev_num, 1);
}

/* 定義platform_driver結構體 */
static struct platform_driver example_platform_driver = {
    .probe      = example_probe,
    .remove     = example_remove,
    .driver     = {
        .name   = "example-pdrv", /* 必須與platform_device的name匹配 */
        .owner  = THIS_MODULE,
        .of_match_table = example_of_match,  /* 設備樹匹配表 */
    },
    .id_table   = example_id_table,         /* ID匹配表 */
};

static int __init example_driver_init(void)
{
    int ret;
    
    printk(KERN_INFO "Example platform driver: registering\n");
    
    /* 註冊platform_driver */
    ret = platform_driver_register(&example_platform_driver);
    if (ret) {
        printk(KERN_ERR "Failed to register platform driver: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "Example platform driver: registered successfully\n");
    return 0;
}

static void __exit example_driver_exit(void)
{
    printk(KERN_INFO "Example platform driver: unregistering\n");
    
    /* 註銷platform_driver */
    platform_driver_unregister(&example_platform_driver);
    
    printk(KERN_INFO "Example platform driver: unregistered successfully\n");
}

module_init(example_driver_init);
module_exit(example_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("Platform Driver Example");
MODULE_VERSION("1.0");

insmod.sh

#!/bin/bash

# 首先清除所有之前的內核日誌
sudo dmesg -c

echo "insmod..."
sudo insmod platform_device_example.ko
sudo insmod platform_driver_example.ko

echo "log:"
dmesg | grep "Example platform"

ls /sys/bus/platform/drivers/example-pdrv
ls /sys/devices/platform/example-pdrv
ls /sys/class/example-driver/example-driver/

remod.sh

#!/bin/bash
echo "rmmod..."
sudo rmmod platform_driver_example.ko
sudo rmmod platform_device_example.ko

echo "lsmod:"
lsmod | grep example

echo "log:"
dmesg | grep "Example platform"

 
echo "
clear log..."
sudo dmesg -c
echo "finish"