linux readl 内核中_linux readl - CSDN
  • 本博实时更新《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)的最新进展。 目前已经完成稿件。 2015年8月9日,china-pub开始上线预售: ... 2015年8月20日,各路朋友报喜...

    本博实时更新《Linux设备驱动开发详解(第3版)》的最新进展。 目前已经完成稿件。

    2015年8月9日,china-pub开始上线预售:

    http://product.china-pub.com/4733972

    2015年8月20日,各路朋友报喜说已经拿到了书。

    本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTEX-A9平台

    本书微信公众号二维码, 如需联系请扫描下方二维码

    [F]是修正或升级;[N]是新增知识点;[D]是删除的内容

     

    第1章 《Linux设备驱动概述及开发环境构建》
    [D]删除关于LDD6410开发板的介绍
    [F]更新新的Ubuntu虚拟机
    [N]添加关于QEMU模拟vexpress板的描述

     

    第2章 《驱动设计的硬件基础》

    [N]增加关于SoC的介绍;
    [N]增加关于eFuse的内容;
    [D]删除ISA总线的内容了;
    [N]增加关于SPI总线的介绍;
    [N]增加USB 3.0的介绍;
    [F]修正USB同步传输方式英文名;
    [D]删除关于cPCI介绍;
    [N]增加关于PCI Express介绍;
    [N]增加关于Xilinx ZYNQ的介绍;
    [N]增加SD/SDIO/eMMC的章节;
    [D]删除“原理图分析的内容”一节;
    [N]增加通过逻辑分析仪看I2C总线的例子;

     

    第3章 《Linux内核及内核编程》

    [N]新增关于3.X内核版本和2015年2月23日 Linux 4.0-rc1
    [N]新增关于内核版本升级流程以及Linux社区开发模式讲解
    [N]新增关于Linux内核调度时间的图式讲解
    [N]新增关于Linux 3.0后ARM架构的变更的讲解
    [N]新增关于TASK_KILLABLE状态的简介
    [N]新增Linux内存管理图式讲解

    [F]修正Kconfig和Makefile中的一些表述
    [D]删除关于x86启动过程讲解
    [N]新增ARM Linux启动过程讲解
    [N]新增关于likely()和unlikely()讲解
    [N]新增toolchain的讲解,以及toolchain的几种浮点模式


    第4章 《Linux内核模块》
    [F]改正关于模块使用非GPL license的问题;
    [F]修正关于__exit修饰函数的内存管理

    第5章 《Linux文件系统与设备文件》
    [F]修正关于文件系统与块设备驱动关系图;
    [N]增加应用到驱动的file操作调用图;
    [N]增加通过netlink接受内核uevent的范例;
    [N]增加遍历sysfs的范例;

    [N]增加为kingston U盘编写udev规则的范例;
    [F]更新udev规则,以符合新版本;
    [N]增加udevadm的讲解;
    [N]高亮Android vold
     
    第6章 《字符设备驱动》
    [F]更新file_operations的定义,升级ioctl()原型;
    [N]增加关于Linux access_ok()的讲解以及Linux内核安全漏洞的说明;
    [F]修正globalmem的编码风格;
    [F]在globalmem支持2个以上实例的时候,从直接2个实例,升级为支持N个实例;

    第7章 《Linux设备驱动中的并发控制》
    [N]绘图深入讲解单核和多核下的各种竞态;
    [N]增加关于编译乱序,执行乱序,编译屏障和内存屏障的讲解;
    [N]增加关于ARM LDREX/STREX指令的讲解;
    [N]对spin_lock单核和多核的使用场景进行深入分析;

    [F]重新整理RCU的讲解方法和实例;
    [F]明确指明信号量已过时;
    [F]将globalmem中使用的信号量换为mutex。


    第8章 《Linux设备驱动中的阻塞与非阻塞I/O》
    [N]新增阻塞和非组塞的时序图
    [F]修正关于等待队列头部、等待队列元素的一些中文说法
    [N]添加等待队列的图形描述
    [F]修正globalfifo的编码风格
    [F]修正globalfifo可读可写的if判断为while判断
    [N]新增select的时序图
    [N]新增EPOLL的章节


    第9章 《Linux设备驱动中的异步通知与异步I/O》
    [F]修正关于glibc AIO支持
    [F]修正关于内核AIO支持
    [F]修正驱动AIO接口
    [D]删除关于驱动AIO支持的错误实例
    [N]高亮C10问题

    第10章 《中断与时钟》
    [N]增加关于ARM GIC的讲解
    [N]增加关于irq_set_affinity() API的讲解
    [N]增加关于devm_request_irq() API的讲解
    [N]增加关于request_any_context_irq() API的讲解

    [F]修正interrupt handler原型
    [F]修正work queue原型
    [N]新增关于Concurrency-managed workqueues讲解
    [N]增加关于ksoftirqd讲解
    [N]增加关于request_threaded_irq()讲解
    [D]删除s3c6410 rtc驱动中断实例
    [N]新增GPIO按键驱动中断实例
    [N]新增hrtimer讲解和实例
    [F]修正second设备编码风格

    第11章 《内存与I/O访问》
    [F]修正关于页表级数的描述,添加PUD
    [F]修正page table walk的案例,使用ARM Linux pin_page_for_write
    [N]新增关于ARM Linux内核空间虚拟地址分布
    [F]修正关于内核空间与用户空间界限
    [N]新增关于DMA、NORMAL和HIGHMEM ZONE的几种可能分布
    [N]新增关于buddy的介绍
    [F]修正关于用户空间malloc的讲解
    [N]增加mallopt()的案例
    [N]增加关于devm_ioremap、devm_request_region()和devm_request_mem_region()的讲解
    [N]增加关于readl()与readl_relaxed()的区别,writel()与writel_relaxed()的区别

    [F]更新vm_area_struct的定义
    [F]修正nopage() callback为fault() callback
    [N]增加io_remap_pfn_range()、vm_iomap_memory()讲解
    [F]强调iotable_init()静态映射目前已不太推荐
    [N]增加关于coherent_dma_mask的讲解
    [N]讲解dma_alloc_coherent()与DMA ZONE关系
    [N]提及了一致性DMA缓冲区与CMA的关系
    [N]增加关于dmaengine驱动和API的讲解

     

    第12章 《工程中的Linux设备驱动》
    [F]更名为《Linux设备驱动的软件架构思想》;
    [N]本章新增多幅图片讲解Linux设备驱动模型;
    [N]新增内容详细剖析为什么要进行设备与驱动的分离,platform的意义;
    [N]新增内容详细剖析为什么Linux设备驱动要分层,枚举多个分层实例;
    [N]新增内容详细剖析Linux驱动框架如何解耦,为什么主机侧驱动要和外设侧驱动分离;
    [N]DM9000实例新增关于在dts中填充平台信息的内容;
    [N]新增内容讲解驱动核心层的3大功能;
    [N]新增内容以面向对象类泛化对比Linux驱动;
    [N]SPI案例部分新增通过dts填充外设信息;

    [F]从tty, LCD章节移出架构部分到本章

     

    第13章 《Linux块设备驱动》
    [N]介绍关于block_device_operations的check_events成员函数
    [N]添加关于磁盘文件系统,I/O调度关系的图形
    [F]更新关于request_queue、request、bio、segment关系的图形
    [F]淘汰elv_next_request
    [F]淘汰blkdev_dequeue_request
    [N]添加关于blk_start_request描述
    [F]淘汰Anticipatory I/O scheduler
    [N]添加关于ZRAM块设备驱动实例
    [F]更新针对内核4.0-rc1的vmem_disk
    [N]添加关于vmem_disk处理I/O过程的图形
    [N]增加关于Linux MMC子系统的描述

     

    第14章 《Linux终端设备驱动》
    [D]整章全部删除,部分架构内容前移到第12章作为驱动分层实例

     

    第15章 《Linux I2C核心、总线与设备驱动》

    [F]修正i2c_adpater驱动的案例
    [N]增加关于在device tree中增加i2c设备的方法的描述

     

    第16章 《Linux网络设备驱动》

    [F]本章顺序从第16章前移到第14章
    [N]澄清sk_buff head、data、tail、end指针关系
    [F]更新sk_buff定义
    [F]澄清skb_put、skb_push、skb_reserve
    [N]增加netdev_priv的讲解,加入实例
    [N]增加关于get_stats()可以从硬件读统计信息的描述

    [F]修正关于net_device_stats结构体的定义位置
    [F]修正关于统计信息的更新方法

     

    第17章 《Linux音频设备驱动》

    [D] 本章直接删除

     

    第18章 《LCD设备驱动》

     

    [D] 本章直接删除,部分架构内容前移到第12章

     

    第19章 《Flash设备驱动》

    [D] 本章直接删除

     

    第20章 《USB主机与设备驱动》
    [F]前移到第16章;
    [F]更名为《USB主机、设备与Gadget驱动》;
    [N]增加关于xHCI的介绍;
    [F]修正usb gadget驱动为function驱动;
    [D]删除OHCI实例;
    [N]添加EHCI讲解和Chipidea EHCI实例;
    [F]修正iso传输实例;
    [F]修正usb devices信息到/sys/kernel/debug/usb/devices
    [N]介绍module_usb_driver;
    [N]介绍usb_function;
    [N]介绍usb_ep_autoconfig;
    [N]介绍usb_otg;

    [D]删除otg_transceiver;

     

    第21章 《PCI设备驱动》
    [D]整章删除

     

    第22章 《Linux设备驱动的调试》

    [F]变为第21章;

    [D]把实验室环境建设相关的节移到第3章;

    [F]修正关于gdb的set step-mode的含义讲解;

    [F]增加关于gdb的set命令的讲解;

    [F]增加gdb call命令的案例

    [D/N]删除手动编译工具链的代码,使用crosstool-ng;
    [N]更新toolchain的下载地址(codesourcery -> memtor),加入linaro下载地址;
    [N]增加pr_fmt的讲解;
    [N]增加关于ignore_loglevel bootargs的讲解;
    [N]增加EARLY_PRINTK和DEBUG_LL的讲解;

    [F]调整proc的范例,创建/proc/test_dir/test_rw;
    [N]修正关于3.10后内核proc实现框架和API的变更;
    [N]增加关于BUG_ON和WARN_ON的讲解
    [F]不再以BDI-2000为例,改为ARM DS-5;
    [N]增加关于ARM streamline性能分析器的介绍;
    [N]增加使用qemu调试linux内核的案例;
    [F]调整Oops的例子,使用globalmem,平台改为ARM;
    [F]更新LTT为LTTng。

     

    第23章 《Linux设备驱动的移植》
    [D]整章删除

     

    全新的章节

     

    第17章  《I2C、SPI、USB架构类比》

     

    本章导读

    本章类比I2C、SPI、USB的结构,从而更进一步帮助读者理解本书第12章的内容,也进一步实证Linux驱动万变不离其宗的道理。

     

    第18章  《Linux设备树(Device Tree)》

     

    本章导读

    本章将介绍Linux设备树(Device Tree)的起源、结构和因为设备树而引起的驱动和BSP变更。

    18.1节阐明了ARM Linux为什么要采用设备树。

    18.2节详细剖析了设备树的结构、结点和属性,设备树的编译方法以及如何用设备树来描述板上的设备、设备的地址、设备的中断号、时钟等信息。

    18.3节讲解了采用设备树后,驱动和BSP的代码需要怎么改,哪些地方变了。

    18.4节补充了一些设备树相关的API定义以及用法。

    本章是相对《Linux设备驱动开发详解(第2版)》全新的一章内容,也是步入内核3.x时代后,嵌入式Linux工程师必备的知识体系。

     

     

     

    第19章  《Linux的电源管理》

     

    本章导读

    Linux在消费电子领域的应用已经铺天盖地,而对于消费电子产品而言,省电是一个重要的议题。

    本章将介绍Linux设备树(Device Tree)的起源、结构和因为设备树而引起的驱动和BSP变更。

    19.1节阐述了Linux电源管理的总体架构。

    19.2~19.8节分别论述了CPUFreq、CPUIdle、CPU热插拔以及底层的基础设施Regulator、OPP以及电源管理的调试工具PowerTop。

    19.9节讲解了系统Suspend to RAM的过程以及设备驱动如何提供对Suspend to RAM的支持。

    19.10节讲解了设备驱动的Runtimesuspend。

    本章是相对《Linux设备驱动开发详解(第2版)》全新的一章内容,也是Linux设备驱动工程师必备的知识体系。

     

     

     

    第20章 《Linux芯片级移植与底层驱动》

     

    本章导读

    本章主要讲解,在一个新的ARM SoC上,如何移植Linux。当然,本章的内容也适合MIPS、PowerPC等其他的体系架构。

    第20.1节先总体上介绍了Linux 3.x之后的内核在底层BSP上进行了哪些优化。

    第20.2节讲解了如何提供操作系统的运行节拍。

    第20.3节讲解了中断控制器驱动,以及它是如何为驱动提供标准接口的。

    第20.4节讲解多核SMP芯片的启动。

    第20.6~20.9节分别讲解了作为Linux运行底层基础设施的GPIO、pinctrl、clock和dmaengine驱动。

    本章相对《Linux设备驱动开发详解(第2版)》几乎是全新的一章内容,有助于工程师理解驱动调用的底层API的来源,以及直接有助于进行Linux的平台移植。

     

    展开全文
  •  readX/writeX() are used to access memory mapped devices. On some  * architectures the memory mapped IO stuff needs to be accessed  * differently. On the simple ...
    
    readX/writeX() are used to access memory mapped devices. On some

     * architectures the memory mapped IO stuff needs to be accessed

     * differently. On the simple architectures, we just read/write the

     * memory location directly.

    writel() 往内存映射的 I/O 空间上写数据,wirtel()  I/O 上写入 32 位数据 (4字节)。

     

     原型:

    #include  

    void writel (unsigned char data , unsigned short addr )

     

     

    readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )

     

    原型:

    #include  

    unsigned char readl (unsigned int addr )

     

    变量    addr  是 I/O 地址。

    返回值 : 从 I/O 空间读取的数值。

     

    定义

    #define readb __raw_readb

    #define readw(addr) __le16_to_cpu(__raw_readw(addr))

    #define readl(addr) __le32_to_cpu(__raw_readl(addr))

    #ifndef __raw_readb

    static inline u8 __raw_readb(const volatile void __iomem *addr)

    {

        return *(const volatile u8 __force *) addr;

    }

    #endif

     

    #ifndef __raw_readw

    static inline u16 __raw_readw(const volatile void __iomem *addr)

    {

        return *(const volatile u16 __force *) addr;

    }

    #endif

     

    #ifndef __raw_readl

    static inline u32 __raw_readl(const volatile void __iomem *addr)

    {

        return *(const volatile u32 __force *) addr;

    }

    #endif

     

    #define writeb __raw_writeb

    #define writew(b,addr) __raw_writew(__cpu_to_le16(b),addr)

    #define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)

    展开全文
  • 相信了解过linux内核的人一定知道在linux内核中有一条非常重要的主线就是总线设备驱动模型。 它是Linux驱动的精髓。不仅在我们今天要讲的I2c驱动中存在,像usb,spi,I2s,platform等设备中也是存在 的。而且在...

    相信了解过linux内核的人一定知道在linux内核中有一条非常重要的主线就是总线设备驱动模型。
    它是Linux驱动的精髓。不仅在我们今天要讲的I2c驱动中存在,像usb,spi,I2s,platform等设备中也是存在
    的。而且在Linux内核的源代码中,不能把总线单独的看,它们有时是互相联系的。


    废话少说,直接进入正题吧。我们以s3c2410为例


    I2c-s3c2410.c这个文件在i2c驱动的整个框架中是属于最底层的。那它主要做了那几件事呢?
    1. ret = platform_driver_register(&s3c2410_i2c_driver);注册一个平台总线驱动,这个函数会去
    平台总线设备的链表中区寻找与之匹配的设备。若有的话就调用s3c2410_i2c_driver->s3c24xx_i2c_probe
    函数。这里就是平台总线驱动的一个机制。
    2. 在s3c2410_i2c_driver->s3c24xx_i2c_probe中
    2.1 主要是一些硬件的操作,比如使能时钟,申请IO资源,寄存器映射,申请中断等
    2.2 ret = i2c_add_numbered_adapter(&i2c->adap);//注册i2c_adapter
            status = i2c_register_adapter(adap);
                res = device_register(&adap->dev);//设备注册
                dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap, i2c_do_add_adapter);//去i2c总线驱动链表中寻找对应的驱动
    3.提供最底层的寄存器操作函数

    #include <linux/kernel.h>

    #include <linux/module.h>

    #include <linux/i2c.h>
    #include <linux/i2c-id.h>
    #include <linux/init.h>
    #include <linux/time.h>
    #include <linux/interrupt.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/err.h>
    #include <linux/platform_device.h>
    #include <linux/clk.h>
    #include <linux/cpufreq.h>

    #include <mach/hardware.h>
    #include <asm/irq.h>
    #include <asm/io.h>

    #include <mach/regs-gpio.h>
    #include <asm/plat-s3c/regs-iic.h>
    #include <asm/plat-s3c/iic.h>

    /* i2c controller state */

    enum s3c24xx_i2c_state {
    STATE_IDLE,
    STATE_START,
    STATE_READ,
    STATE_WRITE,
    STATE_STOP
    };

    struct s3c24xx_i2c {
    spinlock_t lock;
    wait_queue_head_t wait;
    unsigned int suspended:1;

    struct i2c_msg *msg;
    unsigned int msg_num;//数据的大小
    unsigned int msg_idx;//
    unsigned int msg_ptr;

    unsigned int tx_setup;

    enum s3c24xx_i2c_state state;
    unsigned long clkrate;

    void __iomem *regs;
    struct clk *clk;
    struct device *dev;
    struct resource *irq;
    struct resource *ioarea;
    struct i2c_adapter adap;

    #ifdef CONFIG_CPU_FREQ
    struct notifier_block freq_transition;
    #endif
    };

    /* default platform data to use if not supplied in the platform_device
    */

    static struct s3c2410_platform_i2c s3c24xx_i2c_default_platform = {
    .flags = 0,
    .slave_addr = 0x10,
    .bus_freq = 100*1000,
    .max_freq = 400*1000,
    .sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON,
    };

    /* s3c24xx_i2c_is2440()
    *
    * return true is this is an s3c2440
    */

    static inline int s3c24xx_i2c_is2440(struct s3c24xx_i2c *i2c)
    {
    struct platform_device *pdev = to_platform_device(i2c->dev);

    return !strcmp(pdev->name, "s3c2440-i2c");
    }


    /* s3c24xx_i2c_get_platformdata
    *
    * get the platform data associated with the given device, or return
    * the default if there is none
    */

    static inline struct s3c2410_platform_i2c *s3c24xx_i2c_get_platformdata(struct device *dev)
    {
    if (dev->platform_data != NULL)
    return (struct s3c2410_platform_i2c *)dev->platform_data;

    return &s3c24xx_i2c_default_platform;
    }

    /* s3c24xx_i2c_master_complete
    *
    * complete the message and wake up the caller, using the given return code,
    * or zero to mean ok.
    */

    static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)
    {
    dev_dbg(i2c->dev, "master_complete %d\n", ret);

    i2c->msg_ptr = 0;
    i2c->msg = NULL;
    i2c->msg_idx ++;
    i2c->msg_num = 0;
    if (ret)
    i2c->msg_idx = ret;

    wake_up(&i2c->wait);
    }
    /*
    禁止IIC总线应答
    */
    static inline void s3c24xx_i2c_disable_ack(struct s3c24xx_i2c *i2c)
    {
    unsigned long tmp;

    tmp = readl(i2c->regs + S3C2410_IICCON);
    writel(tmp & ~S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);

    }
    /*
    使能IIC总线应答
    */
    static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)
    {
    unsigned long tmp;

    tmp = readl(i2c->regs + S3C2410_IICCON);
    writel(tmp | S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);

    }

    /* irq enable/disable functions */
    /*
    禁止IIC收发中断
    */
    static inline void s3c24xx_i2c_disable_irq(struct s3c24xx_i2c *i2c)
    {
    unsigned long tmp;

    tmp = readl(i2c->regs + S3C2410_IICCON);
    writel(tmp & ~S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);
    }
    /*
    使能IIC收发中断
    */
    static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)
    {
    unsigned long tmp;

    tmp = readl(i2c->regs + S3C2410_IICCON);
    writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);
    }


    /* s3c24xx_i2c_message_start
    *
    * put the start of a message onto the bus
    */
    /*
    函数功能:0.配置IIC设备为主机还是从机
    1.使能收数据或者发数据
    2.确定从机的地址
    3.产生一个起始信号
    */
    static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
    struct i2c_msg *msg)
    {
    unsigned int addr = (msg->addr & 0x7f) << 1;
    unsigned long stat;
    unsigned long iiccon;

    stat = 0;
    stat |= S3C2410_IICSTAT_TXRXEN;//使能收数据或者发数据

    if (msg->flags & I2C_M_RD)
    {
    stat |= S3C2410_IICSTAT_MASTER_RX;//主接受模式
    addr |= 1;
    }
    else
    stat |= S3C2410_IICSTAT_MASTER_TX;//主发送模式

    if (msg->flags & I2C_M_REV_DIR_ADDR)
    addr ^= 1;

    // todo - check for wether ack wanted or not
    s3c24xx_i2c_enable_ack(i2c);

    iiccon = readl(i2c->regs + S3C2410_IICCON);
    writel(stat, i2c->regs + S3C2410_IICSTAT);

    dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
    writeb(addr, i2c->regs + S3C2410_IICDS);//将从机的地址写入到IICDS寄存器中去

    /* delay here to ensure the data byte has gotten onto the bus
    * before the transaction is started */

    ndelay(i2c->tx_setup);

    dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
    writel(iiccon, i2c->regs + S3C2410_IICCON);

    stat |= S3C2410_IICSTAT_START;
    writel(stat, i2c->regs + S3C2410_IICSTAT);//起始信号产生
    }
    /*
    停止IIC传输
    */
    static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret)
    {
    unsigned long iicstat = readl(i2c->regs + S3C2410_IICSTAT);

    dev_dbg(i2c->dev, "STOP\n");

    /* stop the transfer */
    iicstat &= ~ S3C2410_IICSTAT_START;
    writel(iicstat, i2c->regs + S3C2410_IICSTAT);

    i2c->state = STATE_STOP;

    s3c24xx_i2c_master_complete(i2c, ret);
    s3c24xx_i2c_disable_irq(i2c);
    }

    /* helper functions to determine the current state in the set of
    * messages we are sending */

    /* is_lastmsg()
    *
    * returns TRUE if the current message is the last in the set
    */
    /*如果当前消息是集合中的最后一个,则返回真*/
    static inline int is_lastmsg(struct s3c24xx_i2c *i2c)
    {
    return i2c->msg_idx >= (i2c->msg_num - 1);
    }

    /* is_msglast
    *
    * returns TRUE if we this is the last byte in the current message
    */
    /*如果我们这是当前消息中的最后一个字节,则返回真*/
    static inline int is_msglast(struct s3c24xx_i2c *i2c)
    {
    return i2c->msg_ptr == i2c->msg->len-1;
    }

    /* is_msgend
    *
    * returns TRUE if we reached the end of the current message
    */
    /*如果我们到达当前消息的结尾,则返回真*/
    static inline int is_msgend(struct s3c24xx_i2c *i2c)
    {
    return i2c->msg_ptr >= i2c->msg->len;
    }

    /* i2s_s3c_irq_nextbyte
    *
    * process an interrupt and work out what to do
    */

    static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
    {
    unsigned long tmp;
    unsigned char byte;
    int ret = 0;

    switch (i2c->state)
    {

    case STATE_IDLE:
    dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
    goto out;
    break;

    case STATE_STOP:
    dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
    s3c24xx_i2c_disable_irq(i2c);
    goto out_ack;

    case STATE_START:
    /* last thing we did was send a start condition on the
    * bus, or started a new i2c message
    */

    if (iicstat & S3C2410_IICSTAT_LASTBIT &&
    !(i2c->msg->flags & I2C_M_IGNORE_NAK))
    {
    /* ack was not received... */
    /*如果没有收到ACK应答*/
    dev_dbg(i2c->dev, "ack was not received\n");
    s3c24xx_i2c_stop(i2c, -ENXIO);
    goto out_ack;
    }
    /*如果收到ACK应答信号,就继续往下执行*/
    if (i2c->msg->flags & I2C_M_RD)
    i2c->state = STATE_READ;
    else
    i2c->state = STATE_WRITE;

    /* terminate the transfer if there is nothing to do
    * as this is used by the i2c probe to find devices. */

    if (is_lastmsg(i2c) && i2c->msg->len == 0)
    {
    s3c24xx_i2c_stop(i2c, 0);
    goto out_ack;
    }

    if (i2c->state == STATE_READ)
    goto prepare_read;

    /* fall through to the write state, as we will need to
    * send a byte as well */

    case STATE_WRITE:
    /* we are writing data to the device... check for the
    * end of the message, and if so, work out what to do
    */

    if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
    {
    if (iicstat & S3C2410_IICSTAT_LASTBIT)
    {
    dev_dbg(i2c->dev, "WRITE: No Ack\n");

    s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
    goto out_ack;
    }
    }

    retry_write:

    if (!is_msgend(i2c))
    {
    byte = i2c->msg->buf[i2c->msg_ptr++];
    writeb(byte, i2c->regs + S3C2410_IICDS);

    /* delay after writing the byte to allow the
    * data setup time on the bus, as writing the
    * data to the register causes the first bit
    * to appear on SDA, and SCL will change as
    * soon as the interrupt is acknowledged */

    ndelay(i2c->tx_setup);

    }
    else if (!is_lastmsg(i2c))
    {
    /* we need to go to the next i2c message */

    dev_dbg(i2c->dev, "WRITE: Next Message\n");

    i2c->msg_ptr = 0;
    i2c->msg_idx++;
    i2c->msg++;

    /* check to see if we need to do another message */
    if (i2c->msg->flags & I2C_M_NOSTART)
    {

    if (i2c->msg->flags & I2C_M_RD)
    {
    /* cannot do this, the controller
    * forces us to send a new START
    * when we change direction */

    s3c24xx_i2c_stop(i2c, -EINVAL);
    }

    goto retry_write;
    }
    else
    {

    /* send the new start */
    s3c24xx_i2c_message_start(i2c, i2c->msg);
    i2c->state = STATE_START;
    }

    }
    else
    {
    /* send stop */
    s3c24xx_i2c_stop(i2c, 0);
    }
    break;

    case STATE_READ:
    /* we have a byte of data in the data register, do
    * something with it, and then work out wether we are
    * going to do any more read/write
    */

    byte = readb(i2c->regs + S3C2410_IICDS);
    i2c->msg->buf[i2c->msg_ptr++] = byte;

    prepare_read:
    if (is_msglast(i2c))
    {
    /* last byte of buffer */

    if (is_lastmsg(i2c))
    s3c24xx_i2c_disable_ack(i2c);

    }
    else if (is_msgend(i2c))
    {
    /* ok, we've read the entire buffer, see if there
    * is anything else we need to do */

    if (is_lastmsg(i2c))
    {
    /* last message, send stop and complete */
    dev_dbg(i2c->dev, "READ: Send Stop\n");

    s3c24xx_i2c_stop(i2c, 0);
    }
    else
    {
    /* go to the next transfer */
    dev_dbg(i2c->dev, "READ: Next Transfer\n");

    i2c->msg_ptr = 0;
    i2c->msg_idx++;
    i2c->msg++;
    }
    }

    break;
    }

    /* acknowlegde the IRQ and get back on with the work */
    /*清除挂起条件并且继续操作*/
    out_ack:
    tmp = readl(i2c->regs + S3C2410_IICCON);
    tmp &= ~S3C2410_IICCON_IRQPEND;
    writel(tmp, i2c->regs + S3C2410_IICCON);
    out:
    return ret;
    }

    /* s3c24xx_i2c_irq
    *
    * top level IRQ servicing routine
    */
    /*
    IIC中断处理函数
    */
    static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
    {
    struct s3c24xx_i2c *i2c = dev_id;
    unsigned long status;
    unsigned long tmp;

    status = readl(i2c->regs + S3C2410_IICSTAT);

    if (status & S3C2410_IICSTAT_ARBITR)
    {
    // deal with arbitration loss
    dev_err(i2c->dev, "deal with arbitration loss\n");
    }

    if (i2c->state == STATE_IDLE)
    {
    /*如果总线是空闲的*/
    dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");

    tmp = readl(i2c->regs + S3C2410_IICCON);
    tmp &= ~S3C2410_IICCON_IRQPEND;
    writel(tmp, i2c->regs + S3C2410_IICCON);
    goto out;
    }

    /* pretty much this leaves us with the fact that we've
    * transmitted or received whatever byte we last sent */

    i2s_s3c_irq_nextbyte(i2c, status);

    out:
    return IRQ_HANDLED;
    }


    /* s3c24xx_i2c_set_master
    *
    * get the i2c bus for a master transaction
    */
    /*
    判断总线是否忙
    返回值0----不忙
    ETIMEDOUT---忙
    */
    static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)
    {
    unsigned long iicstat;
    int timeout = 400;

    while (timeout-- > 0)
    {
    iicstat = readl(i2c->regs + S3C2410_IICSTAT);

    if (!(iicstat & S3C2410_IICSTAT_BUSBUSY))
    return 0;

    msleep(1);
    }

    dev_dbg(i2c->dev, "timeout: GPEDAT is %08x\n",
    __raw_readl(S3C2410_GPEDAT));

    return -ETIMEDOUT;
    }

    /* s3c24xx_i2c_doxfer
    *
    * this starts an i2c transfer
    */

    static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num)
    {
    unsigned long timeout;
    int ret;

    if (i2c->suspended)
    return -EIO;

    ret = s3c24xx_i2c_set_master(i2c);
    if (ret != 0)
    {
    dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
    ret = -EAGAIN;
    goto out;
    }

    spin_lock_irq(&i2c->lock);

    i2c->msg = msgs;//数据的内容
    i2c->msg_num = num;//数据的大小
    i2c->msg_ptr = 0;
    i2c->msg_idx = 0;
    i2c->state = STATE_START;

    s3c24xx_i2c_enable_irq(i2c);
    s3c24xx_i2c_message_start(i2c, msgs);
    spin_unlock_irq(&i2c->lock);
    /*
    如果msg_mum为0
    */
    timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);

    ret = i2c->msg_idx;

    /* having these next two as dev_err() makes life very
    * noisy when doing an i2cdetect */

    if (timeout == 0)
    dev_dbg(i2c->dev, "timeout\n");
    else if (ret != num)
    dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

    /* ensure the stop has been through the bus */

    msleep(1);

    out:
    return ret;
    }

    /* s3c24xx_i2c_xfer
    *
    * first port of call from the i2c bus code when an message needs
    * transferring across the i2c bus.
    */

    static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
    struct i2c_msg *msgs, int num)
    {
    struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
    int retry;
    int ret;

    for (retry = 0; retry < adap->retries; retry++)
    {

    ret = s3c24xx_i2c_doxfer(i2c, msgs, num);

    if (ret != -EAGAIN)
    return ret;

    dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

    udelay(100);
    }

    return -EREMOTEIO;
    }

    /* declare our i2c functionality */
    static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
    {
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
    }

    /* i2c bus registration info */

    static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
    .master_xfer = s3c24xx_i2c_xfer,
    .functionality = s3c24xx_i2c_func,
    };

    static struct s3c24xx_i2c s3c24xx_i2c = {
    .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
    .wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
    .tx_setup = 50,
    .adap = {
    .name = "s3c2410-i2c",
    .owner = THIS_MODULE,
    .algo = &s3c24xx_i2c_algorithm,
    .retries = 2,
    .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
    },
    };

    /* s3c24xx_i2c_calcdivisor
    *
    * return the divisor settings for a given frequency
    */

    static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted,
    unsigned int *div1, unsigned int *divs)
    {
    unsigned int calc_divs = clkin / wanted;
    unsigned int calc_div1;

    if (calc_divs > (16*16))
    calc_div1 = 512;
    else
    calc_div1 = 16;

    calc_divs += calc_div1-1;
    calc_divs /= calc_div1;

    if (calc_divs == 0)
    calc_divs = 1;
    if (calc_divs > 17)
    calc_divs = 17;

    *divs = calc_divs;
    *div1 = calc_div1;

    return clkin / (calc_divs * calc_div1);
    }

    /* freq_acceptable
    *
    * test wether a frequency is within the acceptable range of error
    */

    static inline int freq_acceptable(unsigned int freq, unsigned int wanted)
    {
    int diff = freq - wanted;

    return (diff >= -2 && diff <= 2);
    }

    /* s3c24xx_i2c_clockrate
    *
    * work out a divisor for the user requested frequency setting,
    * either by the requested frequency, or scanning the acceptable
    * range of frequencies until something is found
    */

    static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
    {
    struct s3c2410_platform_i2c *pdata;
    unsigned long clkin = clk_get_rate(i2c->clk);
    unsigned int divs, div1;
    u32 iiccon;
    int freq;
    int start, end;

    i2c->clkrate = clkin;

    pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent);
    clkin /= 1000; /* clkin now in KHz */

    dev_dbg(i2c->dev, "pdata %p, freq %lu %lu..%lu\n",
    pdata, pdata->bus_freq, pdata->min_freq, pdata->max_freq);

    if (pdata->bus_freq != 0) {
    freq = s3c24xx_i2c_calcdivisor(clkin, pdata->bus_freq/1000,
    &div1, &divs);
    if (freq_acceptable(freq, pdata->bus_freq/1000))
    goto found;
    }

    /* ok, we may have to search for something suitable... */

    start = (pdata->max_freq == 0) ? pdata->bus_freq : pdata->max_freq;
    end = pdata->min_freq;

    start /= 1000;
    end /= 1000;

    /* search loop... */

    for (; start > end; start--)
    {
    freq = s3c24xx_i2c_calcdivisor(clkin, start, &div1, &divs);
    if (freq_acceptable(freq, start))
    goto found;
    }

    /* cannot find frequency spec */

    return -EINVAL;

    found:
    *got = freq;

    iiccon = readl(i2c->regs + S3C2410_IICCON);
    iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);
    iiccon |= (divs-1);

    if (div1 == 512)
    iiccon |= S3C2410_IICCON_TXDIV_512;

    writel(iiccon, i2c->regs + S3C2410_IICCON);

    return 0;
    }

    #ifdef CONFIG_CPU_FREQ

    #define freq_to_i2c(_n) container_of(_n, struct s3c24xx_i2c, freq_transition)

    static int s3c24xx_i2c_cpufreq_transition(struct notifier_block *nb,
    unsigned long val, void *data)
    {
    struct s3c24xx_i2c *i2c = freq_to_i2c(nb);
    unsigned long flags;
    unsigned int got;
    int delta_f;
    int ret;

    delta_f = clk_get_rate(i2c->clk) - i2c->clkrate;

    /* if we're post-change and the input clock has slowed down
    * or at pre-change and the clock is about to speed up, then
    * adjust our clock rate. <0 is slow, >0 speedup.
    */

    if ((val == CPUFREQ_POSTCHANGE && delta_f < 0) ||
    (val == CPUFREQ_PRECHANGE && delta_f > 0)) {
    spin_lock_irqsave(&i2c->lock, flags);
    ret = s3c24xx_i2c_clockrate(i2c, &got);
    spin_unlock_irqrestore(&i2c->lock, flags);

    if (ret < 0)
    dev_err(i2c->dev, "cannot find frequency\n");
    else
    dev_info(i2c->dev, "setting freq %d\n", got);
    }

    return 0;
    }

    static inline int s3c24xx_i2c_register_cpufreq(struct s3c24xx_i2c *i2c)
    {
    i2c->freq_transition.notifier_call = s3c24xx_i2c_cpufreq_transition;

    return cpufreq_register_notifier(&i2c->freq_transition,
    CPUFREQ_TRANSITION_NOTIFIER);
    }

    static inline void s3c24xx_i2c_deregister_cpufreq(struct s3c24xx_i2c *i2c)
    {
    cpufreq_unregister_notifier(&i2c->freq_transition,
    CPUFREQ_TRANSITION_NOTIFIER);
    }

    #else
    static inline int s3c24xx_i2c_register_cpufreq(struct s3c24xx_i2c *i2c)
    {
    return 0;
    }

    static inline void s3c24xx_i2c_deregister_cpufreq(struct s3c24xx_i2c *i2c)
    {
    }
    #endif

    /* s3c24xx_i2c_init
    *
    * initialise the controller, set the IO lines and frequency
    */

    static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
    {
    unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
    struct s3c2410_platform_i2c *pdata;
    unsigned int freq;

    /* get the plafrom data */

    pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent);

    /* inititalise the gpio */

    s3c2410_gpio_cfgpin(S3C2410_GPE15, S3C2410_GPE15_IICSDA);//设置引脚GPE15的功能为IIC数据线
    s3c2410_gpio_cfgpin(S3C2410_GPE14, S3C2410_GPE14_IICSCL);//设置引脚GPE14的功能为IIC时钟线

    /* write slave address */

    writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);//将从地址的数据写入到IICADD寄存器

    dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);

    writel(iicon, i2c->regs + S3C2410_IICCON);//IIC数据收发中断使能和总线应答使能

    /* we need to work out the divisors for the clock... */

    if (s3c24xx_i2c_clockrate(i2c, &freq) != 0)
    {
    writel(0, i2c->regs + S3C2410_IICCON);
    dev_err(i2c->dev, "cannot meet bus frequency required\n");
    return -EINVAL;
    }

    /* todo - check that the i2c lines aren't being dragged anywhere */

    dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
    dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);

    /* check for s3c2440 i2c controller */
    /*
    如果是s3c2400还要设置多主机IIC 总线线控制(IICLC)寄存器
    */
    if (s3c24xx_i2c_is2440(i2c))
    {
    dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);

    writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
    }

    return 0;
    }

    /* s3c24xx_i2c_probe
    *
    * called by the bus driver when a suitable device is found
    */

    static int s3c24xx_i2c_probe(struct platform_device *pdev)
    {
    struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
    struct s3c2410_platform_i2c *pdata;
    struct resource *res;
    int ret;

    pdata = s3c24xx_i2c_get_platformdata(&pdev->dev);//获得私有数据

    /* find the clock and enable it */
    /*设置*/
    i2c->dev = &pdev->dev;
    i2c->clk = clk_get(&pdev->dev, "i2c");
    if (IS_ERR(i2c->clk))
    {
    dev_err(&pdev->dev, "cannot get clock\n");
    ret = -ENOENT;
    goto err_noclk;
    }

    dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);

    clk_enable(i2c->clk);//使能i2c控制器的时钟

    /* map the registers */

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res == NULL)
    {
    dev_err(&pdev->dev, "cannot find IO resource\n");
    ret = -ENOENT;
    goto err_clk;
    }
    /*
    申请IO资源
    */
    i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
    pdev->name);

    if (i2c->ioarea == NULL)
    {
    dev_err(&pdev->dev, "cannot request IO\n");
    ret = -ENXIO;
    goto err_clk;
    }
    /*
    寄存器映射
    */
    i2c->regs = ioremap(res->start, (res->end-res->start)+1);

    if (i2c->regs == NULL) {
    dev_err(&pdev->dev, "cannot map IO\n");
    ret = -ENXIO;
    goto err_ioarea;
    }

    dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res);

    /* setup info block for the i2c core */

    i2c->adap.algo_data = i2c;
    i2c->adap.dev.parent = &pdev->dev;

    /* initialise the i2c controller */

    ret = s3c24xx_i2c_init(i2c);
    if (ret != 0)
    goto err_iomap;

    /* find the IRQ for this unit (note, this relies on the init call to
    * ensure no current IRQs pending
    */

    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);//获得i2c的中断资源
    if (res == NULL) {
    dev_err(&pdev->dev, "cannot find IRQ\n");
    ret = -ENOENT;
    goto err_iomap;
    }
    /*
    申请中断
    */
    ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
    pdev->name, i2c);

    if (ret != 0) {
    dev_err(&pdev->dev, "cannot claim IRQ\n");
    goto err_iomap;
    }

    i2c->irq = res;

    dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
    (unsigned long)res->start);

    ret = s3c24xx_i2c_register_cpufreq(i2c);//注册
    if (ret < 0) {
    dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
    goto err_irq;
    }

    /* Note, previous versions of the driver used i2c_add_adapter()
    * to add the bus at any number. We now pass the bus number via
    * the platform data, so if unset it will now default to always
    * being bus 0.
    */

    i2c->adap.nr = pdata->bus_num;

    ret = i2c_add_numbered_adapter(&i2c->adap);//注册i2c_adapter
    if (ret < 0)
    {
    dev_err(&pdev->dev, "failed to add bus to i2c core\n");
    goto err_cpufreq;
    }

    platform_set_drvdata(pdev, i2c);

    dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id);
    return 0;

    err_cpufreq:
    s3c24xx_i2c_deregister_cpufreq(i2c);

    err_irq:
    free_irq(i2c->irq->start, i2c);

    err_iomap:
    iounmap(i2c->regs);

    err_ioarea:
    release_resource(i2c->ioarea);
    kfree(i2c->ioarea);

    err_clk:
    clk_disable(i2c->clk);
    clk_put(i2c->clk);

    err_noclk:
    return ret;
    }

    /* s3c24xx_i2c_remove
    *
    * called when device is removed from the bus
    */

    static int s3c24xx_i2c_remove(struct platform_device *pdev)
    {
    struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);

    s3c24xx_i2c_deregister_cpufreq(i2c);

    i2c_del_adapter(&i2c->adap);
    free_irq(i2c->irq->start, i2c);

    clk_disable(i2c->clk);
    clk_put(i2c->clk);

    iounmap(i2c->regs);

    release_resource(i2c->ioarea);
    kfree(i2c->ioarea);

    return 0;
    }

    #ifdef CONFIG_PM
    static int s3c24xx_i2c_suspend_late(struct platform_device *dev,
    pm_message_t msg)
    {
    struct s3c24xx_i2c *i2c = platform_get_drvdata(dev);
    i2c->suspended = 1;
    return 0;
    }

    static int s3c24xx_i2c_resume(struct platform_device *dev)
    {
    struct s3c24xx_i2c *i2c = platform_get_drvdata(dev);

    i2c->suspended = 0;
    s3c24xx_i2c_init(i2c);

    return 0;
    }

    #else
    #define s3c24xx_i2c_suspend_late NULL
    #define s3c24xx_i2c_resume NULL
    #endif

    /* device driver for platform bus bits */

    static struct platform_driver s3c2410_i2c_driver = {
    .probe = s3c24xx_i2c_probe,
    .remove = s3c24xx_i2c_remove,
    .suspend_late = s3c24xx_i2c_suspend_late,
    .resume = s3c24xx_i2c_resume,
    .driver = {
    .owner = THIS_MODULE,
    .name = "s3c2410-i2c",
    },
    };

    static int __init i2c_adap_s3c_init(void)
    {
    int ret;

    ret = platform_driver_register(&s3c2410_i2c_driver);
    if (ret == 0) {
    ret = platform_driver_register(&s3c2440_i2c_driver);
    if (ret)
    platform_driver_unregister(&s3c2410_i2c_driver);
    }

    return ret;
    }

    static void __exit i2c_adap_s3c_exit(void)
    {
    platform_driver_unregister(&s3c2410_i2c_driver);
    platform_driver_unregister(&s3c2440_i2c_driver);
    }

    module_init(i2c_adap_s3c_init);
    module_exit(i2c_adap_s3c_exit);

    MODULE_DESCRIPTION("S3C24XX I2C Bus driver");
    MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
    MODULE_LICENSE("GPL");
    MODULE_ALIAS("platform:s3c2410-i2c");

    MODULE_ALIAS("platform:s3c2440-i2c");

    展开全文
  • 本文着重介绍Linux内核中中断处理的始末流程,因此对一些基本的概念应该有所了解。 2.硬件支持 我们知道,CPU有一个INTR引脚,用于接收中断请求信号。 而中断控制器用于提供中断向量,即第几号中断。 3.内核需要...

    在技术面前,多问为什么总是好的,知其然不如知其所以然。

    为什么要有中断?

    1.前言

    本文尽量以设计者的角度去介绍中断。

    本文着重介绍Linux内核中中断处理的始末流程,因此对一些基本的概念应该有所了解。

    2.硬件支持

    我们知道,CPU有一个INTR引脚,用于接收中断请求信号。

    而中断控制器用于提供中断向量,即第几号中断。

    3.内核需要做哪些工作?

    3.1需要一张表

    首先,中断可能来源于外部设备,而外部设备多种多样,也可能来源于CPU,无论如何我们需要区分到底是具体哪种中断,哪个设备产生的。因此,我们首先需要一个表,表中每项表示一种中断,内容可以是指向具体中断服务程序的函数指针,这是最简单的中断向量表,这样,当中断发生时,将中断向量作为中断向量表的下表,就可以直接找到中断服务程序。如图1所示。

    图1-简要中断向量表标题

     

    但是操作系统考虑的更多,比如中断优先级、中断类型标识等。于是就产生了如下图所示中断向量表项。

    中断向量表项结构

    3.2对于用于外设的通用中断,需要一个队列

    但是这样还不行,一方面中断向量有硬件中断控制器产生,因此中断向量数目受限于硬件,无法弹性增长;另一方面,作为通用操作系统,可能存在许多不同的外部设备,也会操作中断向量不够用。因此,解决办法就是共用中断向量。系统为每个中断向量设置一个队列,根据中断源所使用的中断向量,将其挂入到相应的队列中去。其中队列的队列头是irq_desc_t结构体数组。

    typedef struct {
    	unsigned int status;		/* IRQ status */
    	hw_irq_controller *handler;
    	struct irqaction *action;	/* IRQ action list */
    	unsigned int depth;		/* nested irq disables */
    	spinlock_t lock;
    } ____cacheline_aligned irq_desc_t;
    
    extern irq_desc_t irq_desc [NR_IRQS];

    其中,通过其结构体内部的结构体 irqaction 形成一个队列。irqaction结构体可以理解为挂在当前中断向量队列中的特定的设备中断服务程序,如此,同一中断向量队列,就能够区分是哪个中断服务程序属于哪个设备了。

    struct irqaction {
    	void (*handler)(int, void *, struct pt_regs *);    //设备具体中断服务程序
    	unsigned long flags;
    	unsigned long mask;
    	const char *name;
    	void *dev_id;                                     //设备的id表示
    	struct irqaction *next;                           //队列中下一个此结构体
    };
    

    最终形成的中断服务结构图如下所示。

    中断服务结构图

    4.中断向量表的设置

    首先,中断向量表内容从0~0x20均为CPU内部产生的中断,包括除0、页面错等。从0x20开始均为用于外部设备的通用中断(包括中断请求队列),但是0x80系统调用除外。这些表项的内容都是在中断向量表初始化的时候进行设置。

    4.1内部中断的向量表项设置

    对于0~19个内部中断设置由下面函数设置。

    void __init trap_init(void)
    {
    #ifdef CONFIG_EISA
    	if (isa_readl(0x0FFFD9) == 'E'+('I'<<8)+('S'<<16)+('A'<<24))
    		EISA_bus = 1;
    #endif
    
    	set_trap_gate(0,&divide_error);
    	set_trap_gate(1,&debug);
    	set_intr_gate(2,&nmi);
    	set_system_gate(3,&int3);	/* int3-5 can be called from all */
    	set_system_gate(4,&overflow);
    	set_system_gate(5,&bounds);
    	set_trap_gate(6,&invalid_op);
    	set_trap_gate(7,&device_not_available);
    	set_trap_gate(8,&double_fault);
    	set_trap_gate(9,&coprocessor_segment_overrun);
    	set_trap_gate(10,&invalid_TSS);
    	set_trap_gate(11,&segment_not_present);
    	set_trap_gate(12,&stack_segment);
    	set_trap_gate(13,&general_protection);
    	set_trap_gate(14,&page_fault);
    	set_trap_gate(15,&spurious_interrupt_bug);
    	set_trap_gate(16,&coprocessor_error);
    	set_trap_gate(17,&alignment_check);
    	set_trap_gate(18,&machine_check);
    	set_trap_gate(19,&simd_coprocessor_error);
    
    	set_system_gate(SYSCALL_VECTOR,&system_call);
    
    	/*
    	 * default LDT is a single-entry callgate to lcall7 for iBCS
    	 * and a callgate to lcall27 for Solaris/x86 binaries
    	 */
    	set_call_gate(&default_ldt[0],lcall7);
    	set_call_gate(&default_ldt[4],lcall27);
    
    	/*
    	 * Should be a barrier for any external CPU state.
    	 */
    	cpu_init();
    
    #ifdef CONFIG_X86_VISWS_APIC
    	superio_init();
    	lithium_init();
    	cobalt_init();
    #endif
    }
    

    4.2通用中断向量表项设置

    中断类型为中断门的中断向量表设置具体函数如下。

    void set_intr_gate(unsigned int n, void *addr)
    {
            //设置中断向量表项
            //n表示第几项
            //addr表示中断服务的入口程序地址
    	_set_gate(idt_table+n,14,0,addr);
    }
    

    事实上,上面函数参数 void *addr即为上面中断服务结构图中的一系列IRQ0x00_interrupt(),这类函数也是中断服务程序入口函数,即进入中断最先执行的一小段程序,这段程序非常重要,读者可以先猜测一下其功能。

    5.通用中断,中断请求队列初始化

    之前提到,用于外设的通用中断,多个中断源可以共用一个中断向量,因此就有了上文中断服务结构图中的中断请求队列,为了清晰,这里再贴出其结构图。每一个中断请求队列可以想象成一个中断通道,里面容纳了一系列具体设备的中断服务程序。

    ​​​​通用中断请求队列图

    在4.2中我们仅仅是设置了中断向量表项的内容,但是对于通用中断来说,那时仅仅设置了一系列IRQ0x00_interrupt()通用中断入口函数,并没有将各个设备的具体中断服务程序通过结构体irqaction挂到相应的中断请求队列中。

    所以,真正的中断服务要到具体设备初始化程序将中断服务程序通过request_irq()向系统“登记”,挂入某个中断请求队列以后才会发生,下面的函数根据函数参数设置一个irqaction结构体,并根据下标irq挂入相应的irq_desc[irq]所表示的中断请求队列中。

    int request_irq(unsigned int irq, 
    		void (*handler)(int, void *, struct pt_regs *),
    		unsigned long irqflags, 
    		const char * devname,
    		void *dev_id)
    {
    	int retval;
    	struct irqaction * action;
    
    #if 1
    	/*
    	 * Sanity-check: shared interrupts should REALLY pass in
    	 * a real dev-ID, otherwise we'll have trouble later trying
    	 * to figure out which interrupt is which (messes up the
    	 * interrupt freeing logic etc).
    	 */
    	if (irqflags & SA_SHIRQ) {
    		if (!dev_id)
    			printk("Bad boy: %s (at 0x%x) called us without a dev_id!\n", devname, (&irq)[-1]);
    	}
    #endif
    
    	if (irq >= NR_IRQS)
    		return -EINVAL;
    	if (!handler)
    		return -EINVAL;
    
    	action = (struct irqaction *)
    			kmalloc(sizeof(struct irqaction), GFP_KERNEL);
    	if (!action)
    		return -ENOMEM;
            //irqaction具体参数的设置
    	action->handler = handler;            
    	action->flags = irqflags;
    	action->mask = 0;
    	action->name = devname;
    	action->next = NULL;
    	action->dev_id = dev_id;
    
    	retval = setup_irq(irq, action);        //根据irq,将其挂入某个中断请求队列中
    	if (retval)
    		kfree(action);
    	return retval;
    }

    6.中断服务程序的响应

    上述工作已经完成中断机制所需要的所有环境,对于0x20以上的通用中断,外部设备也已经将中断服务程序挂入到相应的中断请求队列中。

    我们现在假设外部程序产生了一次中断请求,该请求通过中断控制器到达了CPU的中断请求引线INTR,并且中断开着,所以当CPU执行完当前指令后就来响应此才中断请求。

    在介绍前,我们首先需要明白中断的执行意味着什么?

    一般外设产生的中断属于突发状况,也就是需要离开正在执行的程序,转向执行中断服务。那么这就面临着几个问题:

    • 当前程序的运行级别和中断程序的运行级别是否相同?这非常重要,会引起堆栈的切换
    • 如何保存当前程序的执行状态,以便中断服务结束后能恢复执行?

    带着这两个问题,我们接着往下看。

    6.1获取中断向量,执行准备工作

    CPU从中断控制器取得中断向量后,就从中断向量表查找该中断向量所指的那一项,还记得​​​​通用中断请求队列图 里面的IRQ0x0x_interrupt()函数吗?它是通用中断向量表项中的一部分,即该通用中断通道的总服务程序入口函数。

    另外,当中断在用户空间发生,当前运行级别为3,而中断服务程序属于内核,其运行级别在中断向量表项中用DPL标识为0,因此,需要切换堆栈:即从用户空间堆栈切换成当前进程的系统空间堆栈。正在运行的堆栈指针存放在寄存器TR所指向的TSS中(这不是重点),因此CPU从TSS中取出系统堆栈指针,完成用户堆栈到系统堆栈的切换。完成堆栈切换后,会将EFLAGS的内容及中断返回地址压入系统堆栈。注意这些压栈操作是由中断指令INT本身发出的,这是还未进入中断通道的总服务程序入口函数IRQ0x0x_interrupt()。

    而至于切换堆栈,本人暂时没弄清楚由哪些代码完成,还请不吝赐教!

    相反,如果中断发生在系统空间,那就无需切换堆栈了,两者差别如下图所示。

    中断发生在用户空间或系统空间的区别

    6.2执行中断总入口函数IRQ0x0YY_interrupt()

    由于此过程非常关键,在此单独叙述。

    以IRQ0x03_interrupt()为例,将函数具体内容列出。

    __asm__ ( \
    "\n" \
    "IRQ0x03_interrupt: \n\t" \
    "pushl $0x03 - 256 \n\t" \        //中断向量号-256,压栈
    "jmp common_interrupt");          //跳转
    
    
    ----------------------------------------------------
    #define BUILD_COMMON_IRQ() \
    asmlinkage void call_do_IRQ(void); \
    __asm__( \
    	"\n" __ALIGN_STR"\n" \
    	"common_interrupt:\n\t" \
    	SAVE_ALL \                                //保存现场,中断前夕所有寄存器内容压栈
    	"pushl $ret_from_intr\n\t" \              //将一个函数地址压栈
    	SYMBOL_NAME_STR(call_do_IRQ)":\n\t" \    
    	"jmp "SYMBOL_NAME_STR(do_IRQ));           //跳转到do_IRQ函数

    首先执行IRQ0x03_interrupt()

    然后,又跳转到common_interrupt

    最后又跳转到do_IRQ函数。

    其中,保存现场的SAVE_ALL操作:

    #define SAVE_ALL \
    	cld; \
    	pushl %es; \
    	pushl %ds; \
    	pushl %eax; \
    	pushl %ebp; \
    	pushl %edi; \
    	pushl %esi; \
    	pushl %edx; \
    	pushl %ecx; \
    	pushl %ebx; \
    	movl $(__KERNEL_DS),%edx; \
    	movl %edx,%ds; \
    	movl %edx,%es;

    因此,在跳转到do_IRQ时,系统堆栈应该如下图所示,结合上面代码,可以说非常清晰了。

    图6.2 进入中断服务程序时,系统堆栈示意图

    堆栈中所保存的这些内容,用于将来恢复进入中断前的程序的执行。

    6.3 do_IRQ函数

    接下来就是执行do_IRQ函数,即要执行具体的中断服务函数了,那么需要具备哪些条件呢?

    • 需要获取中断调用号,即上面的0x03,注意不是中断向量,为什么?
    • 好像没了。。。。

    那么中断调用号从哪获取呢?先看一下do_IRQ函数原型。

    unsigned int do_IRQ(struct pt_regs regs);
    
    struct pt_regs {
    	long ebx;
    	long ecx;
    	long edx;
    	long esi;
    	long edi;
    	long ebp;
    	long eax;
    	int  xds;
    	int  xes;
    	long orig_eax;
    	long eip;
    	int  xcs;
    	long eflags;
    	long esp;
    	int  xss;
    };
    

    请关注其参数 regs,然后再对照图6.2系统堆栈,你会发现什么?

    如何没有想到,再提醒一下函数调用在栈中的构造过程是什么?

    没错,当前所构造出系统堆栈的内容,其实是做了do_IRQ函数的参数,而在调用do_IRQ前,最后压入的ret_from_intr则是do_IRQ的返回地址。

    这样中断服务程序do_IRQ所需的中断调用号有了,返回地址也设置妥了,接下来真的就要进入do_IRQ了。

    结合前面所述,再看中断请求队列,可以想象一下do_IRQ函数具体做了什么?

    • 根据中断请求号,作为数组队列头irq_desc的下标,获取相应的中断请求队列
    • 然后,依次处理队列中具体设备的中断服务程序

    限于篇幅,这里不再贴出代码,具体可以查看arch/i386/kernel/irq.c中的代码。

    在函数的末尾,可能会执行软中断服务程序 do_softirq(),关于软中断的由来请参考其他资料。在函数执行完,就会按照之前精心设置的返回地址ret_from_intr进行返回了。

    7.中断返回

    do_IRQ()函数通过返回地址ret_from_intr会到达entry.S中标号ret_from_intr处:

    ENTRY(ret_from_intr)
    	GET_CURRENT(%ebx)
    	movl EFLAGS(%esp),%eax		# mix EFLAGS and CS
    	movb CS(%esp),%al
    	testl $(VM_MASK | 3),%eax	# return to VM86 mode or non-supervisor?
    	jne ret_with_reschedule
    	jmp restore_all

    上面的操作主要是检查中断前夕,cpu运行于用户空间还是系统空间,若发生于用户空间,转移到ret_with_reschedule,然后最终还是会到达restore_all处。

    ret_with_reschedule:
    	cmpl $0,need_resched(%ebx)
    	jne reschedule
    	cmpl $0,sigpending(%ebx)
    	jne signal_return
    restore_all:
    	RESTORE_ALL
    
    	ALIGN
    signal_return:
    	sti				# we can get here from an interrupt handler
    	testl $(VM_MASK),EFLAGS(%esp)
    	movl %esp,%eax
    	jne v86_signal_return
    	xorl %edx,%edx
    	call SYMBOL_NAME(do_signal)
    	jmp restore_all
    
    
    reschedule:
    	call SYMBOL_NAME(schedule)    # test
    	jmp ret_from_sys_call
    
    
    

    首先,在ret_with_reschedule中判断是否需要进行一次进程调度,需要这转移到reschedule处,接着会转移到ret_from_sys_call,但是从ret_from_sys_call最终还是会到达restore_all处。

    而restore_all操作如下:

    #define RESTORE_ALL	\
    	popl %ebx;	\
    	popl %ecx;	\
    	popl %edx;	\
    	popl %esi;	\
    	popl %edi;	\
    	popl %ebp;	\
    	popl %eax;	\
    1:	popl %ds;	\
    2:	popl %es;	\
    	addl $4,%esp;	\
    3:	iret;	

    这与之前的SAVE_ALL遥相呼应

    #define SAVE_ALL \
    	cld; \
    	pushl %es; \
    	pushl %ds; \
    	pushl %eax; \
    	pushl %ebp; \
    	pushl %edi; \
    	pushl %esi; \
    	pushl %edx; \
    	pushl %ecx; \
    	pushl %ebx; \
    	movl $(__KERNEL_DS),%edx; \
    	movl %edx,%ds; \
    	movl %edx,%es;

    这样,当到达RESTORE_ALL的iret时,iret使CPU从中断返回,和进入中断时对应,如果是从系统态返回到用户态就会将堆栈切换到用户堆栈。

    结束

    这就是Linux内核2.4.0版本的中断机制的内容,当然这里省略了软中断的内容,需要的读者可以参考其他资料。

    技术是为了解决问题的,技术的门槛往往在于不了解技术本身,一旦清楚其过程,也就没有了门槛,但是这对于设计一项技术还远远不够,这就要求我们在了解技术的过程中,多问为什么,知其然不如知其所以然。

    参考资料:

    《Linux内核情景分析》毛德操,胡希明

    展开全文
  • writel和readl,这两个个函数实现在操作系统层,有内存保护的情况下,往一个寄存器或者内存地址写一个数据。先说一下writel:   在arch/alpha/kernel/io.c有 188 void writel(u32 b, volatile void __io...
  • 很多人都在问Linux系统的write调用到底是不是原子的。网上能搜出一大堆文章,基本上要么是翻译一些文献,要么就是胡扯,本文我来结合实例来试着做一个稍微好一点的回答。  先摆出结论吧。结论包含两点,即write...
  • 详解神秘Linux内核

    2015-11-25 09:17:42
    第一步是BIOS从启动设备导入主引导记录(MBR),接下来MBR的代码查看分区表并从活动分区读取GRUB、LILO或SYSLINUX等引导装入程序,之后引导装入程序会加载压缩后的内核映像并将控制权传递给它。内核取得控制权后,...
  • 基本概念: 并发指的是多个执行单元...进程上下文:应用程序陷入内核运行时所处的内核环境 中断上下文:中断服务程序执行时所处的内核环境 抢占式内核:用户程序在执行系统调用期间可以被高优先级进程抢占 非抢占式内
  • 我们知道默认外设I/O资源是不在Linux内核空间的(如sram或硬件接口寄存器等),若需要访问该外设I/O资源,必须先将其地址映射到内核空间来,然后才能在内核空间访问它。 Linux内核访问外设I/O内存资源的方式...
  • Linux驱动开发经常要使用到内核相关的函数,本篇只要介绍在做驱动开发的过程用到的内核函数,为以后开发查询提供方便。 本篇覆盖函数如下 copy_from_user与copy_to_user函数 down_interruptible与down函数 ...
  • ② 在Linux系统,无论是内核程序还是应用程序,都只能使用虚拟地址,而芯片手册给出的硬件寄存器地址或者Ram地址则是物理地址,无法直接使用。因此,我们读写寄存器的第一步就是将它的物理地址映射为虚拟地址。 ...
  • 当我们在u-boot上做了关于nandflash、dm9000网卡、SDRAM和一些基本配置的修改和添加后,我们要让自己修改后的u-boot能启动linux内核,... 我们都知道在u-boot是通过bootm命令来启动linux内核的,bootm会调用do_bootm
  • linux内核中的GPIO系统

    2017-07-19 15:17:51
    软件框架 一、前言 作为一个工作多年的系统工程师,免不了做两件事情:培训新员工和给新员工分配任务。对于那些刚刚从学校出来的学生,一般在开始的时候总是分配一些非常简单的任务,例如GPIO driver、LED driver...
  • linux内核时间子系统
  • linux系统,所有操作的地址都是虚拟地址,都是由linux内核去管理,所以需要将物理地址转换成内核可识别的虚拟地址。 1、ioremap 和 iounmap // 功能:将物理地址映射为虚拟地址 // 参数1:需要映射的物理...
  • 继续Linux内核移植系列,今天介绍如何移植Exynos4412 usb驱动,采用的平台依旧是itop Exynos4412精英板。协议的知识就不介绍了,直接来移植一个可以使用的usb host驱动。一 硬件配置1.1 外部管脚首先usb host
  • 硬件平台:FL2440主机平台:Ubuntu 11.04交叉编译器:arm-linux-gcc 4.3.2原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/6604616本来是想移植最新的内核2.6.39但是总是在编译快完成的...
  • 使用Android模拟器调试linux内核为什么需要调试linux内核如何在Android上调试内核开发环境创建模拟器下载goldfish内核源码编译goldfish内核编译内核遇到的问题使用自己编译的linux内核启动模拟器使用gdb调试内核 ...
  • 我们知道默认外设I/O资源是不在Linux内核空间的(如sram或硬件接口寄存器等),若需要访问该外设I/O资源,必须先将其地址映射到内核空间来,然后才能在内核空间访问它。  Linux内核访问外设I/O内存资源的...
1 2 3 4 5 ... 20
收藏数 4,301
精华内容 1,720
关键字:

linux readl 内核中