linux 低功耗模式中的中断链

来源:互联网 发布:淘宝高品质鹿皮绒女装 编辑:程序博客网 时间:2024/05/14 19:36

linux 系统中,针对低功耗模式

在驱动代码中所需要做出的实现
Low-Power-Consumption/
├── resume
└── suspend

在底层的驱动程序中,需要实现supend和resume函数.注册一个中断唤醒源,一般是GPIO,RTC中断 .然后调用echo mem > state将系统挂起到内存中,这时候SDRAM在进行self-refresh的动作.基本电源的消耗在这里了

目前2.6.kernel已经为你作好了。你只需要在你每个驱动里按照接口函数suspend(…),和resume()将这个驱动的suspend 和resume完成.因为调用apm命令后,kernel会依次调用你注册驱动里的suspend函数,将各种外设都进入节电模式.最后CPU进入power down 模式 相同的,当用RTC或者GPIO中的一个将cpu从power down 模式唤醒.依次也会调用各个驱动里的resume函数将外设唤醒,进入正常工作状态. 当然这个中断唤醒源你必须自己定义enable_irq_wake(irq);

另外,在linux 2.6里

  • 1.要对驱动的suspend里面添加相应设备进入节电状态的代码,
  • 2.在resume里添加相应设备从低功耗返回正常工作模式的代码.

键盘驱动初始化时加入enable_irq_wake,当按键中断来时,就可以唤醒贪睡的内核

在编写驱动时有关中断的操作是经常要用到的,内核驱动中已经编写了一些中断配置函数可以方便的供使用,其与gpio操作配置函数配合使用效果更好。
void disable_irq(unsigned int); //关闭相应中断号中断
void enable_irq(unsigned int);//开启相应中断号中断
int set_irq_type(unsigned int irq, unsigned int type);//设置中断为何种触发模式
void disable_irq_wake(unsigned int irq); //禁止中断唤醒功能
void enable_irq_wake(unsigned int irq);//使能中断唤醒功能

外部中断的驱动初始化时应该完成以下几个步骤:
1.把对应的引脚设置成中断功能
2.设置中断类型(IRQ或者FIQ)
3.设置触发方式
4.使能此中断
设置触发方式的函数接口
int set_irq_type(unsigned int irq, unsigned int type);
中断pending寄存器,主要是用来标识哪个中断产生了.


实例:
这是bcmdhd中的关于suspend和resume的封装。

bcmsdh_suspend

int bcmsdh_suspend(bcmsdh_info_t *bcmsdh){    bcmsdh_os_info_t *bcmsdh_osinfo = bcmsdh->os_cxt;    if (drvinfo.suspend && drvinfo.suspend(bcmsdh_osinfo->context))        return -EBUSY;    return 0;}

bcmsdh_sdmmc_suspend

static int bcmsdh_sdmmc_suspend(struct device *pdev){    int err;    sdioh_info_t *sdioh;    struct sdio_func *func = dev_to_sdio_func(pdev);    mmc_pm_flag_t sdio_flags;    printf("%s Enter func->num=%d\n", __FUNCTION__, func->num);    if (func->num != 2)        return 0;    sdioh = sdio_get_drvdata(func);    err = bcmsdh_suspend(sdioh->bcmsdh);    if (err) {        printf("%s bcmsdh_suspend err=%d\n", __FUNCTION__, err);        return err;    }    sdio_flags = sdio_get_host_pm_caps(func);    if (!(sdio_flags & MMC_PM_KEEP_POWER)) {        sd_err(("%s: can't keep power while host is suspended\n", __FUNCTION__));        return  -EINVAL;    }    /* keep power while host suspended */    err = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);    if (err) {        sd_err(("%s: error while trying to keep power\n", __FUNCTION__));        return err;    }#if defined(OOB_INTR_ONLY)    bcmsdh_oob_intr_set(sdioh->bcmsdh, FALSE);#endif    dhd_mmc_suspend = TRUE;    smp_mb();    printf("%s Exit\n", __FUNCTION__);    return 0;}

bcmsdh_resume

int bcmsdh_resume(bcmsdh_info_t *bcmsdh){    bcmsdh_os_info_t *bcmsdh_osinfo = bcmsdh->os_cxt;    if (drvinfo.resume)        return drvinfo.resume(bcmsdh_osinfo->context);    return 0;}

bcmsdh_sdmmc_resume

static int bcmsdh_sdmmc_resume(struct device *pdev){#if defined(OOB_INTR_ONLY)    sdioh_info_t *sdioh;#endif    struct sdio_func *func = dev_to_sdio_func(pdev);    printf("%s Enter func->num=%d\n", __FUNCTION__, func->num);    if (func->num != 2)        return 0;    dhd_mmc_suspend = FALSE;#if defined(OOB_INTR_ONLY)    sdioh = sdio_get_drvdata(func);    bcmsdh_resume(sdioh->bcmsdh);#endif    smp_mb();    printf("%s Exit\n", __FUNCTION__);    return 0;}

bcmsdh_dev_pm_stay_awake

void bcmsdh_dev_pm_stay_awake(bcmsdh_info_t *bcmsdh){#if !defined(CONFIG_HAS_WAKELOCK) && (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36))    bcmsdh_os_info_t *bcmsdh_osinfo = bcmsdh->os_cxt;    pm_stay_awake(bcmsdh_osinfo->dev);#endif /* !defined(CONFIG_HAS_WAKELOCK) && (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 36)) */}

bcmsdh_oob_intr_register

int bcmsdh_oob_intr_register(bcmsdh_info_t *bcmsdh, bcmsdh_cb_fn_t oob_irq_handler,    void* oob_irq_handler_context){    int err = 0;    bcmsdh_os_info_t *bcmsdh_osinfo = bcmsdh->os_cxt;    SDLX_MSG(("%s: Enter\n", __FUNCTION__));    if (bcmsdh_osinfo->oob_irq_registered) {        SDLX_MSG(("%s: irq is already registered\n", __FUNCTION__));        return -EBUSY;    }#ifdef HW_OOB    printf("%s: HW_OOB enabled\n", __FUNCTION__);#else    printf("%s: SW_OOB enabled\n", __FUNCTION__);#endif    SDLX_MSG(("%s OOB irq=%d flags=%X\n", __FUNCTION__,        (int)bcmsdh_osinfo->oob_irq_num, (int)bcmsdh_osinfo->oob_irq_flags));    bcmsdh_osinfo->oob_irq_handler = oob_irq_handler;    bcmsdh_osinfo->oob_irq_handler_context = oob_irq_handler_context;    bcmsdh_osinfo->oob_irq_enabled = TRUE;    bcmsdh_osinfo->oob_irq_registered = TRUE;#if defined(CONFIG_ARCH_ODIN)    err = odin_gpio_sms_request_irq(bcmsdh_osinfo->oob_irq_num, wlan_oob_irq,        bcmsdh_osinfo->oob_irq_flags, "bcmsdh_sdmmc", bcmsdh);#else    err = request_irq(bcmsdh_osinfo->oob_irq_num, wlan_oob_irq,        bcmsdh_osinfo->oob_irq_flags, "bcmsdh_sdmmc", bcmsdh);#endif /* defined(CONFIG_ARCH_ODIN) */    if (err) {        bcmsdh_osinfo->oob_irq_enabled = FALSE;        bcmsdh_osinfo->oob_irq_registered = FALSE;        SDLX_MSG(("%s: request_irq failed with %d\n", __FUNCTION__, err));        return err;    }#if defined(DISABLE_WOWLAN)    SDLX_MSG(("%s: disable_irq_wake\n", __FUNCTION__));    bcmsdh_osinfo->oob_irq_wake_enabled = FALSE;#else    SDLX_MSG(("%s: enable_irq_wake\n", __FUNCTION__));    err = enable_irq_wake(bcmsdh_osinfo->oob_irq_num);    if (err)        SDLX_MSG(("%s: enable_irq_wake failed with %d\n", __FUNCTION__, err));    else        bcmsdh_osinfo->oob_irq_wake_enabled = TRUE;#endif    return 0;}

此外,以下是对disable_irq() 和 disable_irq_nosync()探讨

disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回. 那么在中断处理程序中应该使用哪一个函数来关闭中断呢?

在”linux设备驱动开发详解”中的按键驱动中, 使用disable_irq来关闭中断, 但是我在测试时进入中断后系统会死在中断处理程序, 而改为disable_irq_nosync则能正常退出中断处理程序.下面从内核代码来找一下原因:
先看一下disable_irq_nosync,内核代码中是这样解释的:

disable_irq_nosync

/** *    disable_irq_nosync - disable an irq without waiting *    @irq: Interrupt to disable * *    Disable the selected interrupt line. Disables and Enables are *    nested. *    Unlike disable_irq(), this function does not ensure existing *    instances of the IRQ handler have completed before returning. * *    This function may be called from IRQ context. */void disable_irq_nosync(unsigned int irq){    struct irq_desc *desc = irq_to_desc(irq);    unsigned long flags;    if (!desc)        return;    chip_bus_lock(irq, desc);    spin_lock_irqsave(&desc->lock, flags);    __disable_irq(desc, irq, false);    spin_unlock_irqrestore(&desc->lock, flags);    chip_bus_sync_unlock(irq, desc);}

关闭中断后程序返回, 如果在中断处理程序中, 那么会继续将中断处理程序执行完.

disable_irq

/** * disable_irq - disable an irq and wait for completion * @irq: Interrupt to disable * * Disable the selected interrupt line. Enables and Disables are * nested. * This function waits for any pending IRQ handlers for this interrupt * to complete before returning. If you use this function while * holding a resource the IRQ handler may need you will deadlock. * * This function may be called - with care - from IRQ context. */void disable_irq(unsigned int irq){        struct irq_desc *desc = irq_desc + irq;        if (irq >= NR_IRQS)                return;        disable_irq_nosync(irq);        if (desc->action)                synchronize_irq(irq);}

关闭中断并等待中断处理完后返回.从代码中可以看到, disable_irq先是调用了disable_irq_nosync, 然后检测desc->action是否为1. 在中断处理程序中, action是置1的, 所以进入synchronize_irq函数中.

synchronize_irq

/** * synchronize_irq - wait for pending IRQ handlers (on other CPUs) * @irq: interrupt number to wait for * * This function waits for any pending IRQ handlers for this interrupt * to complete before returning. If you use this function while * holding a resource the IRQ handler may need you will deadlock. * * This function may be called - with care - from IRQ context. */void synchronize_irq(unsigned int irq){ struct irq_desc *desc = irq_to_desc(irq); unsigned int status; if (!desc)  return; do {  unsigned long flags;  /*   * Wait until we're out of the critical section. This might   * give the wrong answer due to the lack of memory barriers.   */  while (desc->status & IRQ_INPROGRESS)   cpu_relax();  /* Ok, that indicated we're done: double-check carefully. */  spin_lock_irqsave(&desc->lock, flags);  status = desc->status;  spin_unlock_irqrestore(&desc->lock, flags);  /* Oops, that failed? */ } while (status & IRQ_INPROGRESS); /*  * We made sure that no hardirq handler is running. Now verify  * that no threaded handlers are active.  */ wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));}

注释中说明该函数是在等待中断处理程序的结束, 这也是disable_irq与disable_irq_nosync不同的主要所在. 但是在中断处理函数中调用会发生什么情况呢? 进入中断处理函数前IRQ_INPROGRESS会被__setup_irq设置, 所以程序会一直陷在while循环中, 而此时内核以经被独占, 这就导致系统死掉.

总结:
由于在disable_irq中会调用synchronize_irq函数等待中断返回, 所以在中断处理程序中不能使用disable_irq, 否则会导致cpu被synchronize_irq独占而发生系统崩溃.

0 0
原创粉丝点击