ioremap和ioport_map

本文深入探讨了Linux内核中IO寄存器的两种地址映射方式——映射到IO地址空间(portio)与映射到内存地址空间(mmio)。详细解释了ioremap函数的作用与ioport_map函数的实现,以及如何通过统一的访问函数实现portio与mmio的统一访问。同时,提供了IO端口访问与IO内存访问的具体流程,帮助读者理解Linux内核中IO资源的高效管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.基础介绍

IO寄存器(内存)有两种地址映射方式,即映射到IO地址空间(portio),映射到内存地址空间(mmio)
在linux-2.6之前,前者使用inb/outb(......)访问,后者通过readb/writeb(......)来访问(访问前必须使用ioremap将IO物理地址映射到虚拟地址)。

ioremap函数作用如前所述,就是将IO物理地址映射成虚拟地址,这样readb/writeb可以访问映射后的地址。mmio映射的IO内存,其物理地址已经在4G地址空间内,与内存的编址方式相同。因此ioremap的作用实际上就是为这一段物理地址建立页表,返回驱动程序可以访问的虚拟地址。

ioport_map函数的目的是试图提供与ioremap一致的虚拟地址空间。这样不论portio还是mmio都可以使用统一的访问函数:ioread8/iowrite8(......)。
我们进入ioport_map和ioport_unmap函数:

  1. void __iomem *ioport_map(unsigned long port, unsigned int nr)
  2. {
  3.     if (port > PIO_MASK)
  4.         return NULL;
  5.     return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
  6. }

  7. void ioport_unmap(void __iomem *addr)
  8. {
  9.     /* Nothing to do */
  10. }

其实函数很简单,ioport_map仅仅是将port加上PIO_OFFSET(64k),而ioport_unmap则什么都不做。这样portio的64k空间就被映射到虚拟地址的64k~128k之间,而ioremap返回的虚拟地址则肯定在3G之上。这样portio和mmio的虚拟地址就被统一起来。
再看ioread8的源码,其实现也就是对虚拟地址进行了判断,以区分portio和mmio,然后分别使用inb/outb,和readb/writeb来读写。
  1. unsigned int fastcall ioread8(void __iomem *addr)
  2. {
  3.     IO_COND(addr, return inb(port), return readb(addr));
  4. }

  5. #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)
  6. #define IO_COND(addr, is_pio, is_mmio) do { \
  7.     unsigned long port = (unsigned long __force)addr; \
  8.         if (port < PIO_RESERVED) { \
  9.             VERIFY_PIO(port); \
  10.             port &= PIO_MASK; \
  11.             is_pio; \
  12.         } else { \
  13.             is_mmio; \
  14.         } \
  15. } while (0)

  16. 展开:
  17. unsigned int fastcall ioread8(void __iomem *addr)
  18. {
  19.     unsigned long port = (unsigned long __force)addr;
  20.     if( port < 0x40000UL ) {
  21.         BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );
  22.         port &= PIO_MASK;
  23.         return inb(port);
  24.     }else{
  25.         return readb(addr);
  26.     }
  27. }

  28. 二.图文讲解

    1.IO端口访问 

    一种方法是:直接使用IO端口操作函数:在设备打开或驱动模块被加载时申请IO端口区域,之后使用inb(),outb()等进行端口访问,最后在设备关闭或驱动被卸载时释放IO端口范围。流程如下:

       

       另外一种途径是:将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射,流程如下:

       

    2.IO内存访问

      上边是IO端口的访问方法,至于IO内存的访问方法是:首先调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release_mem_region()申请的IO内存资源。

    流程如下:

       

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值