linux uart寄存器 代替 printk,启用了DMA的UART Tx模式

本文详细介绍了在Linux 3.4环境下,针对omap4460pandaboard的UART设备使用DMA进行数据传输的驱动程序实现。作者展示了如何映射DMA寄存器、请求中断、初始化UART和DMA以及处理用户空间的数据写入。然而,在DMA通道启用后,遇到了中断服务例程(ISR)无限循环的问题。作者指出,ISR可能需要正确处理每个DMA请求,并且可能需要调整DMA配置以避免无限循环。此外,讨论了DMA配置中的一些细节,如地址模式、优先级和通道设置。尽管问题已解决,但文章并未提供具体的ISR代码解决方案。

我已经为传输模式下的DMA写入了一个简单的设备驱动程序,并启用了DMA以及中断。 我使用的硬件是带有Linux 3.4的omap 4460 pandaboard。启用了DMA的UART Tx模式

下面我分享了代码的相关部分。 在打开相:

dma_map = ioremap(UART4_DMA_REG,0x1350);

if(dma_map == NULL) {

printk(KERN_INFO " unable to io_remap DMA region\n");

return -ENOMEM;

}

printk(KERN_INFO "DMA mapping successful\n");

irq_val = request_irq(45,uart_handler,IRQF_DISABLED,"uart_int",NULL);

if(irq_val) {

printk(KERN_INFO "cannot assign the requested irq\n");

return -1;

}

else {

printk(KERN_INFO "Requested irq successful\n");

}

其中UART4_DMA_REG是DMA的基地址寄存器0x4a056000和请求IRQ是45,其线SDMA的1中断。 在此UART寄存器初始化和DMA启用后。 现在用户调用write函数将100个字节的数据复制到内核空间的缓冲区中。

下面的代码显示的写功能:

ssize_t uart_write(struct file *filp,const char __user *buff, size_t count, loff_t *offp)

{

int no_of_bytes;

int maxbytes;

struct device *udevice = &devi;

int ret_mask;

char *kbuf = kmalloc(100,GFP_KERNEL|GFP_DMA);

maxbytes = BUFF_SIZE - *offp;

if(count > maxbytes)//overflow of buffer

no_of_bytes = maxbytes;

else

no_of_bytes = count;

if(no_of_bytes == 0)

printk(KERN_INFO "Nothing is there to write to device\n");

bytes_written = no_of_bytes - copy_from_user(kbuf,buff,no_of_bytes);//copy_from_user()returns remaining bytes.

printk(KERN_INFO "Write Completed\n");

Uindex = 0;

*offp += bytes_written;

ret_mask = dma_set_coherent_mask(udevice,DMA_BIT_MASK(32));

if(!ret_mask)

printk(KERN_INFO "set mask success \n");

else

printk(KERN_INFO "SET MASK NOT SUCCESS \n");

bus_addr = dma_map_single(udevice,kbuf,size,DMA_TO_DEVICE);

printk(KERN_INFO "dma_map_single completed");

dma_init();

return bytes_written;

}

dma_init();此函数初始化DMA寄存器,使在软件触发模式中的信道。

void dma_init()

{

unsigned int ccr_val;

unsigned int csdp_val;

irq_line = 1; //for tx line 1 is considered

dma_cha_line = 0; //for tx line 0 is considered

/* Interrupt Enabled in DMA4_IRQENABLE_Lj and DMA4_CICRi registers */

iowrite32(0x1,(dma_map + 0x0018 + (4 * irq_line)));//to unmask the interrupt DMA4_IRQENABLE_Lj

iowrite32(0x8,(dma_map + 0x0088 + (0x60 * dma_cha_line)));//condition to generate interrupt CICR reg

/* Set the Read Port & Write Port access in CSDP */

csdp_val = ioread32(dma_map + 0x0090 + (0x60 * dma_cha_line));

csdp_val &= ~(0x3 << 7);//Source

csdp_val &= ~(0x3 << 14);//Destination

csdp_val &= ~(0x3 << 16);//Writing mode without posted

csdp_val &= ~(0x1 << 21);//little endian source

csdp_val &= ~(0x1 << 19);//little endian destination

csdp_val &= ~(0x1 << 13);//destination not packed

csdp_val &= ~(0x1 << 6);//source not packed

csdp_val &= ~(0x3);//ES is set to 8 bits

iowrite32(csdp_val,(dma_map + 0x0090 + (0x60 * dma_cha_line)));

/* CEN register configuration */

iowrite32(100,(dma_map + 0x0094 +(0x60 * dma_cha_line)));//EN is set to 1

/* CFN register configuration */

iowrite32(1,(dma_map + 0x0098 +(0x60 * dma_cha_line)));//FN is set to 1

/* Set the Channel Source & Destination start address */

iowrite32(bus_addr,(dma_map + 0x009C + (0x60 * dma_cha_line)));//Source

iowrite32(io_map,(dma_map + 0x00a0 + (0x60 * dma_cha_line)));//Destination

/* CCR configuration */

ccr_val = ioread32(dma_map + 0x0080 + (0x60 * dma_cha_line));

/* Set the Read Port & Write Port addressing mode in CCR */

/*

ccr_val &= ~(0x3 << 12);//Source - constant address mode

ccr_val |= (0x1 << 14);//Destination - post incremented address mode-set 14th bit and clear 15th bit

ccr_val &= ~(0x1 << 15);

*/

ccr_val |= (0x1 << 12);//source - post incremented address mode-set 12th bit and clear 13th bit

ccr_val &= ~(0x1 << 13);

ccr_val &= ~(0x3 << 14);//destination- constant address mode - clear 14 and 15th bit

ccr_val |= (0x1 << 26);//high priority on write

ccr_val &= ~(0x1 << 6);//low priority on read

ccr_val &= ~(0x1f);//CCR[4:0]

ccr_val &= ~(0x3 << 19);//CCR [19:20] to 0

ccr_val |= (0x1 << 7);// Set the channel enable bit in CCR

iowrite32(ccr_val,(dma_map + 0x0080 + (0x60 * dma_cha_line)));

/*CSEI,CSFI,CDEI,CDFI*/

iowrite32(1,(dma_map + 0x00a4 +(0x60 * dma_cha_line)));

iowrite32(1,(dma_map + 0x00a8 +(0x60 * dma_cha_line)));

iowrite32(1,(dma_map + 0x00ac +(0x60 * dma_cha_line)));

iowrite32(1,(dma_map + 0x00b0 +(0x60 * dma_cha_line)));

printk(KERN_INFO "DMA registers configured\n");

}

现在的问题是:一旦通道被使能(只是调用dma_init后()),ISR(处理)被调用,进入无限循环。我的ISR应该在写模式下包含什么?

2014-05-21

ddpd

+0

您可以添加ISR代码吗?另外,你说通道在dma_init()调用后启用,但在dma_init()之后只有一个返回。 –

+0

@ScottLawson:谢谢你的回复。但我已经解决了这些问题。首先,由于这是一个字符设备,所以DMA传输必须每个DMA请求发生1个元素。我已经将它配置为每个DMA请求传送1块。其次,按照元素传输编程指南,不需要配置DMA4_CEN和DMA4_CFN寄存器。但它只在配置这些寄存器时才起作用。 –

+0

酷!你应该发布这个答案并接受它。鼓励你回答你自己的问题。 –

static int serial_omap_probe(struct platform_device *pdev)//输入为ttyO4 { struct uart_omap_port *up; struct resource *mem, *irq, *dma_tx, *dma_rx; //获取预先配置的平台数据 //omap_uart_port_info包含UART控制器配置信息 /* 典型配置: static struct omap_uart_port_info omap4_uart_info = { .flags = UPF_BOOT_AUTOCONF, .uartclk = 48000000, .dma_enabled = true, .dma_rx_buf_size = 4096, .dma_rx_timeout = 20, }; */ struct omap_uart_port_info *omap_up_info = pdev->dev.platform_data; int ret = -ENOSPC; if (pdev->dev.of_node) omap_up_info = of_get_uart_port_info(&pdev->dev); //获得平台设备注册时指定的资源 //获取UART寄存器内存区域 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(&pdev->dev, "no mem resource?\n"); return -ENODEV; } //获取中断号 irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!irq) { dev_err(&pdev->dev, "no irq resource?\n"); return -ENODEV; } //通过名称获取TX/RX DMA通道资源 if (!request_mem_region(mem->start, resource_size(mem), pdev->dev.driver->name)) { dev_err(&pdev->dev, "memory region already claimed\n"); return -EBUSY; } dma_rx = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); if (!dma_rx) { ret = -EINVAL; goto err; } dma_tx = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); if (!dma_tx) { ret = -EINVAL; goto err; } up = kzalloc(sizeof(*up), GFP_KERNEL); if (up == NULL) { ret = -ENOMEM; goto do_release_region; } up->pdev = pdev; up->port.dev = &pdev->dev;//关联设备 up->port.type = PORT_OMAP;//端口类型标识 up->port.iotype = UPIO_MEM;//内存映射IO up->port.irq = irq->start;//中断号 up->port.regshift = 2;//寄存器偏移 up->port.fifosize = 64;//FIFO大小 up->port.ops = &serial_omap_pops;//硬件操作函数集 //通过设备树别名或平台ID确定端口号 if (pdev->dev.of_node) up->port.line = of_alias_get_id(pdev->dev.of_node, "serial");//设备树别名 else up->port.line = pdev->id;//平台ID if (up->port.line < 0) { dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n", up->port.line); ret = -ENODEV; goto err; } //寄存器内存映射 sprintf(up->name, "OMAP UART%d", up->port.line); up->port.mapbase = mem->start; up->port.membase = ioremap(mem->start, resource_size(mem)); if (!up->port.membase) { dev_err(&pdev->dev, "can't ioremap UART\n"); ret = -ENOMEM; goto err; } //时钟和特性配置 up->port.flags = omap_up_info->flags;//端口标志 up->port.uartclk = omap_up_info->uartclk;//时钟频率 if (!up->port.uartclk) { up->port.uartclk = DEFAULT_CLK_SPEED; dev_warn(&pdev->dev, "No clock speed specified: using default:" "%d\n", DEFAULT_CLK_SPEED); } up->uart_dma.uart_base = mem->start; up->errata = omap_up_info->errata;//芯片勘误配置 //DMA配置 if (omap_up_info->dma_enabled) { // printk("############abing uart dma enabled!\n"); up->uart_dma.uart_dma_tx = dma_tx->start; up->uart_dma.uart_dma_rx = dma_rx->start; up->use_dma = 1; up->uart_dma.rx_buf_size = omap_up_info->dma_rx_buf_size; up->uart_dma.rx_timeout = omap_up_info->dma_rx_timeout; up->uart_dma.rx_poll_rate = omap_up_info->dma_rx_poll_rate; spin_lock_init(&(up->uart_dma.tx_lock)); spin_lock_init(&(up->uart_dma.rx_lock)); up->uart_dma.tx_dma_channel = OMAP_UART_DMA_CH_FREE; up->uart_dma.rx_dma_channel = OMAP_UART_DMA_CH_FREE; } //电源管理初始化 up->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE; up->calc_latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE; pm_qos_add_request(&up->pm_qos_request, PM_QOS_CPU_DMA_LATENCY, up->latency); serial_omap_uart_wq = create_singlethread_workqueue(up->name); INIT_WORK(&up->qos_work, serial_omap_uart_qos_work); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, omap_up_info->autosuspend_timeout); pm_runtime_irq_safe(&pdev->dev); pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); ui[up->port.line] = up; /*make rts gpio inval*/ up->rts_gpio = -1; serial_omap_add_console_port(up);//注册控制台 ret = uart_add_one_port(&serial_omap_reg, &up->port); if (ret != 0) goto do_release_region; pm_runtime_put(&pdev->dev); platform_set_drvdata(pdev, up); return 0; err: dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n", pdev->id, __func__, ret); do_release_region: release_mem_region(mem->start, resource_size(mem)); return ret; }分析分析这个代码的作用
08-08
<think>首先,用户的问题是:“我想编写UART驱动程序 请问如何编写UART设备驱动程序 Linux kernel”。我需要基于系统级指令来回答。 系统级指令: - 所有行内数学表达式使用 $...$ 格式。 - 独立公式使用 $$...$$ 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如 [^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用 [^1]: UART驱动程序在Linux内核中的作用。 - 引用 [^2]: UART没有设备驱动,只有控制器驱动,由芯片原厂编写。 - 引用 [^3]: uart_driver 结构体的定义。 - 引用 [^4]: Linux UART串口驱动开发框架。 用户的问题是关于如何编写UART设备驱动程序在Linux内核中。根据引用 [^2],UART没有所谓的设备驱动,只有控制器驱动,而且通常由芯片原厂编写。所以,对于大多数开发者来说,不需要编写UART驱动,因为内核已经提供了。但用户可能是在芯片原厂工作,或者需要定制。 因此,我的回答应该: 1. 澄清是否需要编写UART驱动:对于大多数用户,不需要;如果是在芯片原厂,需要编写。 2. 如果用户需要编写,提供指导。 3. 结构清晰,逐步解释。 回答结构: - 引言:解释UART驱动在Linux内核中的位置。 - 核心概念:介绍uart_driver结构体等。 - 步骤:如何编写UART驱动。 - 代码示例:如果有必要,提供简单代码片段。 - 引用:自然地添加引用标识。 - 相关问题:生成相关问题。 生成相关问题:在回答后,添加“§§相关问题§§”部分。 使用中文回答。 现在,构建回答内容: 1. **引言**:UART驱动程序负责管理UART设备的通信。在Linux内核中,UART驱动通常由芯片原厂提供,但对于定制开发,可能需要编写。参考 [^1] 和 [^2]。 2. **核心概念**: - UART驱动涉及uart_driver结构体,用于注册驱动。 - 结构体定义在 include/linux/serial_core.h 中。 - 引用 [^3] 提供了uart_driver的定义。 3. **编写步骤**: - 步骤1: 定义uart_driver结构体。 - 步骤2: 实现必要的操作函数,如 probe, remove。 - 步骤3: 注册驱动到内核。 - 步骤4: 处理中断和DMA(如果需要)。 - 参考 [^4] 的框架。 4. **代码示例**:提供一个简单的代码框架。 5. **注意事项**:强调驱动开发需要内核模块开发经验。 6. **相关问题**:生成3个相关问题。 添加引用标识:在相关段落末尾添加 [^1], [^2], 等。 确保LaTeX:如果有数学表达式,使用$...$,但UART驱动可能没有数学,所以可能不需要。 最终回答结构: - 标题或开头。 - 逐步解释。 - 代码块(如果需要)。 - 相关问题。</think>### 如何编写UART设备驱动程序在Linux内核中 在Linux内核中,UART(Universal Asynchronous Receiver/Transmitter)驱动程序负责管理串口设备的通信,包括数据传输、中断处理和设备配置。UART驱动通常分为两部分:**控制器驱动**(由芯片原厂提供)和**设备驱动**(用户层接口)。根据您的需求,我将逐步解释编写UART设备驱动程序的流程。需要注意的是,对于大多数开发者,内核已提供标准驱动(如8250驱动),无需从头编写;但如果您在芯片原厂工作或需要定制硬件支持,以下是关键步骤和框架。参考引用[^2],UART驱动本质上是“控制器驱动”,开发需基于内核提供的UART核心层和TTY子系统。 #### 1. **理解UART驱动框架** Linux内核的UART驱动基于分层架构: - **TTY层**:提供用户空间接口(如/dev/ttyS*)。 - **UART核心层**:处理通用逻辑(如缓冲管理和设备注册)。 - **UART驱动层**:硬件相关部分,由开发者实现(包括初始化、中断处理等)。 核心结构体是`uart_driver`(定义在`include/linux/serial_core.h`),它包含驱动信息,如设备名称、主次设备号等。引用[^3]详细描述了其定义: ```c struct uart_driver { struct module *owner; // 模块指针,通常为THIS_MODULE const char *driver_name; // 驱动名称(如"my_uart") const char *dev_name; // 设备名称前缀(如"ttyS") int major; // 主设备号 int minor; // 次设备号 int nr; // 支持的端口数量 struct console *cons; // 控制台设备指针 struct uart_state *state; // 端口状态(由核心层初始化) struct tty_driver *tty_driver; // 关联的TTY驱动 }; ``` 开发时,您只需实现驱动层,核心层和TTY层由内核处理[^1][^4]。 #### 2. **编写UART驱动的基本步骤** ##### 步骤1: 定义驱动结构体和操作函数 创建一个内核模块,定义`uart_driver`和`uart_ops`(硬件操作函数集)。例如: ```c #include <linux/module.h> #include <linux/serial_core.h> #include <linux/tty_flip.h> static struct uart_driver my_uart_driver = { .owner = THIS_MODULE, .driver_name = "my_uart", .dev_name = "ttyS", .major = 240, // 分配未使用的主设备号 .minor = 0, // 起始次设备号 .nr = 1, // 支持一个UART端口 }; // 硬件操作函数示例 static void my_uart_start_tx(struct uart_port *port) { // 实现发送数据逻辑 } static struct uart_ops my_uart_ops = { .start_tx = my_uart_start_tx, // 实现其他函数:.stop_tx, .startup, .shutdown等 }; ``` 关键函数包括: - `startup`:初始化硬件。 - `shutdown`:关闭设备。 - `start_tx`:启动数据传输。 - `handle_irq`:中断处理程序。 ##### 步骤2: 注册驱动和添加端口 在模块初始化函数中,注册驱动并添加UART端口(基于硬件地址和中断): ```c static int __init my_uart_init(void) { int ret; // 注册UART驱动 ret = uart_register_driver(&my_uart_driver); if (ret) return ret; // 添加一个UART端口(假设硬件信息已知) struct uart_port port; memset(&port, 0, sizeof(port)); port.ops = &my_uart_ops; port.line = 0; // 端口索引 port.iotype = UPIO_MEM; // 内存映射IO port.mapbase = 0x12345678; // 硬件寄存器基地址 port.irq = 5; // 中断号 port.uartclk = 1843200; // 波特率时钟 ret = uart_add_one_port(&my_uart_driver, &port); if (ret) uart_unregister_driver(&my_uart_driver); return ret; } static void __exit my_uart_exit(void) { uart_unregister_driver(&my_uart_driver); } module_init(my_uart_init); module_exit(my_uart_exit); MODULE_LICENSE("GPL"); ``` ##### 步骤3: 实现中断和DMA处理 - 在中断处理函数中,读取数据并调用`tty_insert_flip_char`将数据推送到TTY层。 - 如果使用DMA,需配置DMA通道并处理传输完成中断。 #### 3. **关键注意事项** - **硬件依赖**:驱动必须匹配具体硬件(如寄存器映射和中断)。参考芯片手册设置`mapbase`和`irq`。 - **内核版本兼容性**:不同内核版本API可能变化(如Linux 4.x vs 5.x)。建议参考内核文档`Documentation/serial/driver.rst`。 - **调试工具**:使用`printk`输出日志,或`stty`工具测试设备。 - **性能优化**:对于高速传输启用DMA或环形缓冲区减少CPU负载。 根据引用[^4],开发时可参考现有驱动(如`drivers/tty/serial/8250/8250_core.c`)作为模板。如果您非芯片原厂开发者,建议优先使用内核标准驱动,避免重复开发[^2]。 #### 4. **完整示例框架** 以下是一个简化代码框架(基于Linux 4.9+): ```c // my_uart_driver.c #include <linux/module.h> #include <linux/serial_core.h> #include <linux/io.h> static struct uart_ops my_uart_ops = { .tx_empty = my_tx_empty, // 检查发送缓冲区空 .set_mctrl = my_set_mctrl, // 设置调制解调器控制 .startup = my_startup, // 启动设备 .shutdown = my_shutdown, // 关闭设备 // 实现其他必要函数 }; static irqreturn_t my_uart_interrupt(int irq, void *dev_id) { struct uart_port *port = dev_id; // 处理接收中断 unsigned char ch = readb(port->membase); tty_insert_flip_char(&port->state->port, ch, TTY_NORMAL); tty_flip_buffer_push(&port->state->port); return IRQ_HANDLED; } static int my_startup(struct uart_port *port) { // 申请中断 request_irq(port->irq, my_uart_interrupt, IRQF_SHARED, "my_uart", port); // 初始化硬件寄存器 return 0; } // 模块初始化和注册(同步骤2) ``` 编译后,加载模块:`insmod my_uart_driver.ko`。设备节点将出现在`/dev/ttyS*`。 #### 5. **开发资源** - **内核文档**:`Documentation/serial/` 提供详细指南。 - **测试工具**:使用`minicom`或`screen`验证通信。 - **安全建议**:在用户空间测试前,确保驱动正确处理错误(如超时和缓冲区溢出)。 编写UART驱动需要扎实的内核编程经验。如果遇到问题,建议先分析内核源码(如8250驱动)或参考社区资源[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值