linux内核的水很深,分享一下关于__raw_writel()出错的问题

本文探讨了在Linux内核驱动开发过程中遇到的一个问题,即使用__raw_writel函数导致的错误。作者在尝试加载和卸载PWM驱动时,发现错误源于该函数。经过调试,排除了地址定义错误的可能性,但具体原因仍未解决。最后通过修改代码,错误得以消除,提示读者内核开发的复杂性,鼓励深入讨论。
摘要由CSDN通过智能技术生成
先上一个测试代码来说明一下:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/io.h>

#define at91_pwm_read(reg)                  __raw_readl(reg)   
#define at91_pwm_write(reg, val)            __raw_writel((val), reg)

#define PWMC_BASE 0xfffb8000
#define PWM_MR_OFF              0x00000000    ///< PWM Mode Register offset.
//#define PWM_MR     __phys_to_pfn(PWMC_BASE + PWM_MR_OFF) ///< PWM Mode Register.  //这里是关键
#define PWM_MR     (PWMC_BASE + PWM_MR_OFF) ///< PWM Mode Register.

static int __init my_led_init(void)
{
	unsigned long value;
	

	
	value = at91_pwm_read(PWM_MR);
	printk("0xfffb8000 = %x", value);
	at91_pwm_write(PWM_MR, 0xA2); 
	value = at91_pwm_read(PWM_MR);
	printk("0xfffb8000 = %x", value);
	
	at91_set_gpio_output(AT91_PIN_PD15, 0);
	at91_set_gpio_value(AT91_PIN_PD15, 0);
    return 0;
}
static void __exit my_led_exit(void)
{
   
	//at91_set_gpio_output(AT91_PIN_PD15, 0);
	at91_set_gpio_value(AT91_PIN_PD15, 1);

    return;
}

module_init(my_led_init)
module_exit(my_led_exit)

MODULE_LICENSE("GPL");


其实这就是一个简单的加载和卸载驱动的模块,因为我昨天做的pwm的驱动下载到开发板的时候总是报错,在代码里面找了很久都没有找到原因,

今天早上我把模块单独拿出来试试,最后确定是__raw_writel和__raw_readl的原因, 开始以为是我定义的地址出错了.后来调试了很久还是没有找到.上一个出错的信息;

<1>Unable to handle kernel paging request at virtual address fffb8000
pgd = c6c14000
[fffb8000] *pgd=705a5031, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1]
Modules linked in: myled(+) [last unloaded: myled]
CPU: 0    Not tainted  (2.6.30 #28)
PC is at my_led_init+0x10/0x58 [myled]
LR is at do_one_initcall+0x4c/0x17c
pc : [<bf00f010>]    lr : [<c01222d4>]    psr: 60000013
sp : c6c2df08  ip : 00000000  fp : bec68c94
r10: 00000000  r9 : c6c2c000  r8 : c0483140
r7 : bf00f000  r6 : 001e5060  r5 : bf00c01c  r4 : fffb8fff
r3 : 00000000  r2 : c6c2c000  r1 : 00000001  r0 : bf00f000
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 0005317f  Table: 76c14000  DAC: 00000015
Process insmod (pid: 450, stack limit = 0xc6c2c268)
Stack: (0xc6c2df08 to 0xc6c2e000)
df00:                   00000000 00000ae4 bf00c320 c01222d4 c888e2ce c79cf420 
df20: c888ea10 00000016 00000018 c888e3d0 bf00c32c c888e768 c6c14000 00000000 
df40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
df60: 00000ae4 bf00c320 001e5060 00000ae4 bf00c320 001e5060 00000000 c0122ea8 
df80: 00000000 c0157f24 c7815740 ffffffff 001e44c0 00000ae4 bec68db1 000002ec 
dfa0: 00000080 c0122d00 00000ae4 bec68db1 001e5060 00000ae4 001e5050 bec68db1 
dfc0: 00000ae4 bec68db1 000002ec 00000080 00000000 00000002 00000000 bec68c94 
dfe0: bec68ab8 bec68aa8 0002cdbc 00009300 20000010 001e5060 00000000 00000000 
[<bf00f010>] (my_led_init+0x10/0x58 [myled]) from [<c01222d4>] (do_one_initcall+0x4c/0x17c)
[<c01222d4>] (do_one_initcall+0x4c/0x17c) from [<c0157f24>] (sys_init_module+0x90/0x194)
[<c0157f24>] (sys_init_module+0x90/0x194) from [<c0122d00>] (ret_fast_syscall+0x0/0x2c)
Code: e92d4030 e59f5048 e3e04a47 e24dd004 (e5141fff) 
---[ end trace 55d98dffd0e5be81 ]---
Segmentation fault
serial_core.c是Linux内核的一个串口驱动核心文件,主要负责串口设备的初始化、读写和断处理等操作。以下是该文件的代码解读: 1. 头文件包含 ``` #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> #include <linux/init.h> #include <linux/serial.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/console.h> #include <linux/console_struct.h> #include <linux/serial_core.h> #include <linux/serial_reg.h> #include <linux/io.h> #include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/dmaengine.h> #include <linux/dma-mapping.h> ``` 该文件包含了一些常用的头文件,如内核头文件、串口头文件、断头文件等。 2. 宏定义 ``` #define serial_outp(port, value) writeb((value), (port)) #define serial_inp(port) readb(port) #define serial_outw(port, value) writew((value), (port)) #define serial_inw(port) readw(port) #define serial_outl(port, value) writel((value), (port)) #define serial_inl(port) readl(port) ``` 这些宏定义了读写串口寄存器的操作,通过调用内核提供的读写函数实现。 3. 结构体定义 ``` struct uart_port { spinlock_t lock; /* 锁 */ void __iomem *membase; /* MMIO基地址 */ unsigned char *mapbase; /* 端口映射基地址 */ unsigned char *membase_addr; /* MMIO地址 */ unsigned char *mapbase_addr; /* 端口映射地址 */ unsigned int iotype:2; /* 端口类型 */ unsigned int irq; /* 断号 */ unsigned int uartclk; /* 时钟 */ unsigned int fifosize; /* FIFO大小 */ unsigned int flags; /* 标志 */ unsigned int regshift; /* 寄存器位移 */ unsigned int iobase; /* 端口基地址 */ unsigned int iolen; /* 端口长度 */ unsigned int regtype:2; /* 寄存器类型 */ unsigned int uartclk_high; /* 高位时钟 */ struct uart_state *state; /* 串口状态 */ struct uart_ops *ops; /* 串口操作 */ struct uart_driver *uartclk_reg; /* 时钟寄存器 */ struct console *cons; /* 控制台 */ struct device *dev; /* 设备 */ struct dma_chan *dma; /* DMA通道 */ struct dma_async_tx_descriptor *tx_dma; /* DMA传输描述符 */ struct dma_async_tx_descriptor *rx_dma; /* DMA传输描述符 */ unsigned int capabilities; /* 串口功能 */ unsigned int type; /* 串口类型 */ unsigned int line; /* 串口线路 */ unsigned int uartclk_rate; /* 时钟频率 */ struct ktermios *termios; /* 终端参数 */ struct ktermios *gpios; /* GPIO配置 */ struct delayed_work work; /* 延迟工作队列 */ }; ``` 该结构体定义了串口端口的各种信息,如锁、基地址、断号、时钟、标志等。 4. 函数定义 该文件包含了众多函数定义,具体解读如下: (1) uart_get_baud_rate()函数 ``` unsigned int uart_get_baud_rate(struct uart_port *port, struct ktermios *termios, struct ktermios *old, unsigned int min, unsigned int max) ``` 该函数用于获取波特率,根据终端参数计算波特率并返回。 (2) uart_update_timeout()函数 ``` void uart_update_timeout(struct uart_port *port, unsigned int cflag) ``` 该函数用于更新串口超时时间,根据终端参数计算超时时间并更新。 (3) uart_register_driver()函数 ``` int uart_register_driver(struct uart_driver *uart_drv) ``` 该函数用于注册串口驱动,将驱动加入到内核串口驱动链表。 (4) uart_unregister_driver()函数 ``` void uart_unregister_driver(struct uart_driver *uart_drv) ``` 该函数用于注销串口驱动,从内核串口驱动链表移除。 (5) uart_add_one_port()函数 ``` int uart_add_one_port(struct uart_driver *drv, struct uart_port *port) ``` 该函数用于添加一个串口端口,将其加入到驱动的端口列表。 (6) uart_remove_one_port()函数 ``` void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port) ``` 该函数用于移除一个串口端口,从驱动的端口列表删除。 (7) uart_suspend_port()函数 ``` int uart_suspend_port(struct uart_driver *drv, struct uart_port *port) ``` 该函数用于挂起一个串口端口,暂停其读写操作。 (8) uart_resume_port()函数 ``` int uart_resume_port(struct uart_driver *drv, struct uart_port *port) ``` 该函数用于恢复一个串口端口,重新开始读写操作。 (9) uart_change_speed()函数 ``` void uart_change_speed(struct uart_port *port, unsigned int new_speed) ``` 该函数用于改变串口的波特率,重新计算超时时间。 (10) uart_handle_sysrq_char()函数 ``` int uart_handle_sysrq_char(struct uart_port *port, unsigned int ch) ``` 该函数用于处理系统请求字符,将其发送到串口设备。 (11) uart_insert_char()函数 ``` void uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun, unsigned int ch, unsigned int flag) ``` 该函数用于向串口设备插入一个字符,处理溢出和错误等情况。 (12) uart_write_wakeup()函数 ``` void uart_write_wakeup(struct uart_port *port) ``` 该函数用于唤醒串口设备的写操作,将等待的进程唤醒。 (13) uart_flush_buffer()函数 ``` void uart_flush_buffer(struct uart_port *port) ``` 该函数用于刷新串口设备的缓冲区,清空缓冲区的数据。 (14) uart_start()函数 ``` void uart_start(struct uart_port *port) ``` 该函数用于启动串口设备的读操作,开始接收数据。 (15) uart_stop()函数 ``` void uart_stop(struct uart_port *port) ``` 该函数用于停止串口设备的读操作,停止接收数据。 (16) uart_shutdown()函数 ``` void uart_shutdown(struct uart_port *port) ``` 该函数用于关闭串口设备,释放资源。 (17) uart_handle_cts_change()函数 ``` void uart_handle_cts_change(struct uart_port *port, unsigned int status) ``` 该函数用于处理CTS(清除发送)信号的变化,控制发送操作。 (18) uart_handle_dcd_change()函数 ``` void uart_handle_dcd_change(struct uart_port *port, unsigned int status) ``` 该函数用于处理DCD(数据载波检测)信号的变化,控制读操作。 (19) uart_handle_dsr_change()函数 ``` void uart_handle_dsr_change(struct uart_port *port, unsigned int status) ``` 该函数用于处理DSR(数据终端就绪)信号的变化,控制读操作。 (20) uart_get_stats()函数 ``` void uart_get_stats(struct uart_port *port, struct uart_icount *icount) ``` 该函数用于获取串口设备的统计信息,包括接收、发送、错误等信息。 5. 总结 serial_core.c是Linux内核的一个串口驱动核心文件,包含了众多的函数和结构体定义,实现了串口设备的初始化、读写、断处理等操作。对于Linux内核开发人员来说,了解该文件的代码实现,对于理解串口驱动的原理和实现具有重要意义。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值