Linux 电源管理在ARM上的实现原理

ARM 144浏览

http://blog.chinaunix.net/uid-12461657-id-3148498.html

内核版本号:linux2.6.32

 

 

由于arm系统中没有bios设备, 所以只能为arm系统创建一个虚拟的字符设备与用户空间进行通讯. 即在apm中实现一个misc设备,实质上也是一个字符设备, misc设备的主设备号是10, 而apm_bios作为个misc设备, 次设备号是134。 Linux2.6.30.10内核的/drivers/char/apm-emulation.c提供了apm_bios的驱动模型,也就是系统进入睡眠的入口函数,更早的版本的接口文件为:arch/arm/kernel/apm.c
在apm-emulation.c中:

#define APM_MINOR_DEV 134

这个apm_bios设备通过ioctl系统调用和用户空间进行通讯, 即当用户进程通过ioctl发来suspend命令时, 它就传给内核, 使系统进入suspend状态.

1,初始化

static int __init apm_init(void)
{
    int ret;
    if (apm_disabled) {
    printk(KERN_NOTICE "apm: disabled on user request.n");
    return -ENODEV;
    }
//创建一个线程, 用于处理事件队列, 工作函数是kapmd
kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
if (IS_ERR(kapmd_tsk)) {
   ret = PTR_ERR(kapmd_tsk);
   kapmd_tsk = NULL;
   goto out;
   }
wake_up_process(kapmd_tsk);


//通过proc,向用户空间输出apm信息
#ifdef CONFIG_PROC_FS
proc_create("apm", 0, NULL, &apm_proc_fops);
#endif

//注册misc设备
ret = misc_register(&apm_device);
if (ret)
   goto out_stop;
ret = register_pm_notifier(&apm_notif_block);
if (ret)
   goto out_unregister;
return 0;
out_unregister:
   misc_deregister(&apm_device);
out_stop:
   remove_proc_entry("apm", NULL);
kthread_stop(kapmd_tsk);
out:
   return ret;
}
//注册结构为:
static struct file_operations apm_bios_fops = {
  .owner = THIS_MODULE,
  .read = apm_read,
  .poll = apm_poll,
  .ioctl = apm_ioctl,
  .open = apm_open,
  .release = apm_release,
};
static struct miscdevice apm_device = {
  .minor = APM_MINOR_DEV,
  .name = "apm_bios",
  .fops = &apm_bios_fops
};
这样就我们就可以像对一般的设备文件一样,读取apm_bios的相关信息了。

2,结构中函数实现

当一个用户进程打开apm_bios设备时, 它就会调用这个函数

static int apm_open(struct inode * inode, struct file * filp)
{
//这个关键是apm_user结构变量as,它是用户和apm内核部分沟通的桥梁,当有apm事件发生时,就把event挂到apm_user的queue上,这样当用户读时就会读到相关事件然后处理。

struct apm_user *as;
lock_kernel();
//分配一个apm_user结构, 来表示一个用户进程
as = kzalloc(sizeof(*as), GFP_KERNEL);

//读写等权限设置
if (as) {
  as->suser = capable(CAP_SYS_ADMIN);
  as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
  as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;

//将这个用户加入用户队列
  down_write(&user_list_lock);
  list_add(&as->list, &apm_user_list);
  up_write(&user_list_lock);
//这是一个传递私有数据的一个通用方式
  filp->private_data = as;
  }

unlock_kernel();
return as ? 0 : -ENOMEM;
}

当用户空间进程去读这个设备时, 这个函数就会被调用. 这个函数的主要作用是将事件读出到用户空间.
static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
{
  struct apm_user *as = fp->private_data;
  apm_event_t event;
  int i = count, ret = 0;
  if (count < sizeof(apm_event_t))
  return -EINVAL;
//队列空, 且进程非阻塞读, 立刻返回
  if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
  return -EAGAIN;

//否则等待到队列非空为止,
  wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
//将队列中的事件复制给用户空间
  while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
      event = queue_get_event(&as->queue);
      ret = -EFAULT;
      if (copy_to_user(buf, &event, sizeof(event)))
      break;
//设置状态
      mutex_lock(&state_lock);
      if (as->suspend_state == SUSPEND_PENDING &&
      (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
            as->suspend_state = SUSPEND_READ;
      mutex_unlock(&state_lock);

      buf += sizeof(event);
      i -= sizeof(event);
   }
   if (i < count)
   ret = count - i;
   return ret;
}
//这个poll/select的后端实现, 用于查询有没有数据可读
static unsigned int apm_poll(struct file *fp, poll_table * wait)
{
   struct apm_user *as = fp->private_data;
   poll_wait(fp, &apm_waitqueue, wait);
   return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
}

//这个是这个设备的核心函数, 用于内核与用户空间交互
static int apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
{
   struct apm_user *as = filp->private_data;
   int err = -EINVAL;
//只有超级用户才能执行回复
   if (!as->suser || !as->writer)
      return -EPERM;
   switch (cmd) {
     case APM_IOC_SUSPEND:
         mutex_lock(&state_lock);
            as->suspend_result = -EINTR;
            switch (as->suspend_state) {
//这个就是当user读取到event时的状态,这是发送这个事件,意味着这是回应ack
                 case SUSPEND_READ:
                     as->suspend_state = SUSPEND_ACKED;
                     atomic_dec(&suspend_acks_pending);
                     mutex_unlock(&state_lock);
                     wake_up(&apm_suspend_waitqueue);
                     freezer_do_not_count();
                     wait_event(apm_suspend_waitqueue, as->suspend_state == SUSPEND_DONE);
                     freezer_count();
                     break;
                 case SUSPEND_ACKTO:
                      as->suspend_result = -ETIMEDOUT;
                      mutex_unlock(&state_lock);
                      break;
                 default:
                      as->suspend_state = SUSPEND_WAIT;
                      mutex_unlock(&state_lock);
                      as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
            }
         mutex_lock(&state_lock);
         err = as->suspend_result;
         as->suspend_state = SUSPEND_NONE;
         mutex_unlock(&state_lock);
         break;
         }
    return err;
}

3, 事件队列函数

static void queue_event(apm_event_t event)
{
  struct apm_user *as;

  down_read(&user_list_lock);
  list_for_each_entry(as, &apm_user_list, list) {
  if (as->reader)
//这个是将这个事件发给每个需要知道事件的apm_user
      queue_add_event(&as->queue, event);
  }
  up_read(&user_list_lock);

//唤醒等待读的进程
  wake_up_interruptible(&apm_waitqueue);
}

static void queue_add_event(struct apm_queue *q, apm_event_t event)
{
  q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
  if (q->event_head == q->event_tail) {
     static int notified;
     if (notified++ == 0)
         printk(KERN_ERR "apm: an event queue overflowedn");
      q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
   }
  q->events[q->event_head] = event;
}

4,所有用户回复了,可以执行ioctl中的pm_suspend了这部分说明kernel里面的电源管理的核心函数,这部分的代码在/kernel/power/suspend.c中

int pm_suspend(suspend_state_t state)
{
  if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
  return enter_state(state);
  return -EINVAL;
}
调用enter_state(),同样在supend.c中
int enter_state(suspend_state_t state)
{
  int error;
  if (!valid_state(state))
    return -ENODEV;
//获得锁, 参见注释
  if (!mutex_trylock(&pm_mutex))
    return -EBUSY;
  printk(KERN_INFO "PM: Syncing filesystems ... ");
  sys_sync();
  printk("done.n");
//prepare阶段
  pr_debug("PM: Preparing system for %s sleepn", pm_states[state]);
  error = suspend_prepare();
  if (error)
     goto Unlock;
  if (suspend_test(TEST_FREEZER))
     goto Finish;
//进入阶段
  pr_debug("PM: Entering %s sleepn", pm_states[state]);
  error = suspend_devices_and_enter(state);
//完成挂起, 恢复状态
Finish:
  pr_debug("PM: Finishing wakeup.n");
  suspend_finish();
Unlock:
  mutex_unlock(&pm_mutex);
  return error;
}

4.1准备阶段,为进入supend状态做准备

static int suspend_prepare(void)
{
  int error;
  if (!suspend_ops || !suspend_ops->enter)
     return -EPERM;
//allocate a console
  pm_prepare_console();
//Run suspend notifiers
  error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
  if (error)
     goto Finish;
  error = usermodehelper_disable();
  if (error)
     goto Finish;
//进程处理
   error = suspend_freeze_processes();
   if (!error)
      return 0;
   suspend_thaw_processes();
   usermodehelper_enable();
Finish:
  pm_notifier_call_chain(PM_POST_SUSPEND);
  pm_restore_console();
  return error;
}

4.2 进入阶段,挂起设备。

int suspend_devices_and_enter(suspend_state_t state)
{
  int error;
  if (!suspend_ops)
    return -ENOSYS;
  if (suspend_ops->begin) {
     error = suspend_ops->begin(state);
     if (error)
        goto Close;
  }
  suspend_console();
  suspend_test_start();
//挂起设备
  error = dpm_suspend_start(PMSG_SUSPEND);
  if (error) {
     printk(KERN_ERR "PM: Some devices failed to suspendn");
     goto Recover_platform;
   }
   suspend_test_finish("suspend devices");
   if (suspend_test(TEST_DEVICES))
      goto Recover_platform;

   suspend_enter(state);

Resume_devices:
  suspend_test_start();
  dpm_resume_end(PMSG_RESUME);
  suspend_test_finish("resume devices");
  resume_console();
Close:
  if (suspend_ops->end)
  suspend_ops->end();
  return error;

Recover_platform:
  if (suspend_ops->recover)
     suspend_ops->recover();
  goto Resume_devices;
}

4.2.1挂起设备

int dpm_suspend_start(pm_message_t state)
{
  int error;
  might_sleep();
  error = dpm_prepare(state);
  if (!error)
     error = dpm_suspend(state);
     return error;
}

函数dpm_suspend_start()最后会调用dpm_suspend函数来挂起每个设备。

static int dpm_suspend(pm_message_t state)
{
  struct list_head list;
  int error = 0;
  INIT_LIST_HEAD(&list);
  mutex_lock(&dpm_list_mtx);
//遍历设备链表, 当一个设备被注册进系统时, 它同时会被加入到这个dpm_list队列中
  while (!list_empty(&dpm_list)) {
    struct device *dev = to_device(dpm_list.prev);
    get_device(dev);
    mutex_unlock(&dpm_list_mtx);
//挂起这个设备
    error = suspend_device(dev, state);
    mutex_lock(&dpm_list_mtx);
       if (error) {
          pm_dev_err(dev, state, "", error);
          put_device(dev);
          break;
       }
       dev->power.status = DPM_OFF;
//加入list队列, 用于以后唤醒
       if (!list_empty(&dev->power.entry))
           list_move(&dev->power.entry, &list);
           put_device(dev);
   }
   list_splice(&list, dpm_list.prev);
   mutex_unlock(&dpm_list_mtx);
   return error;
}

此函数又会调用 suspend_device()函数挂起相应得设备,这个函数调用相应设备的suspend实现设备挂起. 所以说, 系统挂起时, 设备也应该做相应的工作, 由于设备的特殊性, 这些就是留在设备里面来实现了.

static int suspend_device(struct device *dev, pm_message_t state)
{
  int error = 0;
  down(&dev->sem);
  if (dev->class) {
     if (dev->class->pm) {
        pm_dev_dbg(dev, state, "class ");
        error = pm_op(dev, dev->class->pm, state);
     } else if (dev->class->suspend) {
        pm_dev_dbg(dev, state, "legacy class ");
        error = dev->class->suspend(dev, state);
        suspend_report_result(dev->class->suspend, error);
     }
     if (error)
        goto End;
  }
  if (dev->type) {
     if (dev->type->pm) {
         pm_dev_dbg(dev, state, "type ");
         error = pm_op(dev, dev->type->pm, state);
     } else if (dev->type->suspend) {
         pm_dev_dbg(dev, state, "legacy type ");
         error = dev->type->suspend(dev, state);
         suspend_report_result(dev->type->suspend, error);
     }
  if (error)
     goto End;
  }
if (dev->bus) {
   if (dev->bus->pm) {
       pm_dev_dbg(dev, state, "");
       error = pm_op(dev, dev->bus->pm, state);
   } else if (dev->bus->suspend) {
       pm_dev_dbg(dev, state, "legacy ");
       error = dev->bus->suspend(dev, state);
       suspend_report_result(dev->bus->suspend, error);
   }
  }
End:
  up(&dev->sem);
  return error;
}

4.2.2 进入阶段

回到suspend_devices_and_enter()再看suspend_enter(state)函数
static int suspend_enter(suspend_state_t state)
{
  int error;
//调用体系结构相关的函数, 这是在系统初始化的时候注册的
  if (suspend_ops->prepare) {
     error = suspend_ops->prepare();
     if (error)
        return error;
  }

  error = device_power_down(PMSG_SUSPEND);
  if (error) {
     printk(KERN_ERR "PM: Some devices failed to power downn");
     goto Platfrom_finish;
  }
  if (suspend_ops->prepare_late) {
     error = suspend_ops->prepare_late();
     if (error)
        goto Power_up_devices;
  }
  if (suspend_test(TEST_PLATFORM))
     goto Platform_wake;
  error = disable_nonboot_cpus();
  if (error || suspend_test(TEST_CPUS))
     goto Enable_cpus;
  arch_suspend_disable_irqs();
  BUG_ON(!irqs_disabled());
  error = sysdev_suspend(PMSG_SUSPEND);
  if (!error) {
     if (!suspend_test(TEST_CORE))
//调用体系结构相关的函数, 这是在系统初始化的时候注册的
         error = suspend_ops->enter(state);
     sysdev_resume();
    }
  arch_suspend_enable_irqs();
  BUG_ON(irqs_disabled());

Enable_cpus:
  enable_nonboot_cpus();
Platform_wake:
  if (suspend_ops->wake)
     suspend_ops->wake();
Power_up_devices:
  device_power_up(PMSG_RESUME);
Platfrom_finish:
  if (suspend_ops->finish)
//调用体系结构相关的函数, 这是在系统初始化的时候注册的
     suspend_ops->finish();
  return error;
}
这个suspend_ops就是在体系结构初始化的时候注册进来的,
接着看arch/arm/plat-s3c/pm.c
int __init s3c_pm_init(void)
{
  printk("S3C Power Management, Copyright 2004 Simtec Electronicsn");
  suspend_set_ops(&s3c_pm_ops);
  return 0;
}

static struct platform_suspend_ops s3c_pm_ops = {
  .enter = s3c_pm_enter,
  .prepare = s3c_pm_prepare,
  .finish = s3c_pm_finish,
  .valid = suspend_valid_only_mem,
};
这就是实现三个状态转换的三个钩子函数.
这个函数较为简单, 只是将/kerenel/power/main.c里的全局变量suspend_ops设置成
s3c_pm_ops而已了.
这就完成了这个全局变量的初始化.后续对suspend_ops的访问实质上都是访问s3c_pm_ops.
void suspend_set_ops(struct platform_suspend_ops *ops)
{
  mutex_lock(&pm_mutex);
  suspend_ops = ops;
  mutex_unlock(&pm_mutex);
}
最后看看函数实现:
static int s3c_pm_prepare(void)
{

  s3c_pm_check_prepare();
  return 0;
}

static void s3c_pm_finish(void)
{
  s3c_pm_check_cleanup();
}
这里主要是s3c_pm_nter函数:
static int s3c_pm_enter(suspend_state_t state)
{
//用于保存16个通用寄存器的栈
  static unsigned long regs_save[16];

  s3c_pm_debug_init();
  S3C_PMDBG("%s(%d)n", __func__, state);
  if (pm_cpu_prep == NULL || pm_cpu_sleep == NULL) {
     printk(KERN_ERR "%s: error: no cpu sleep functionn", __func__);
   return -EINVAL;
  }

//检查允许的唤醒中断
  if (!any_allowed(s3c_irqwake_intmask, s3c_irqwake_intallow) &&
     !any_allowed(s3c_irqwake_eintmask, s3c_irqwake_eintallow)) {

     printk(KERN_ERR "%s: No wake-up sources!n", __func__);
     printk(KERN_ERR "%s: Aborting sleepn", __func__);
     return -EINVAL;
   }

//寄存器的物理地址
  s3c_sleep_save_phys = virt_to_phys(regs_save);
  S3C_PMDBG("s3c_sleep_save_phys=0xlxn", s3c_sleep_save_phys);
//保存不属于driver的核心寄存器, driver的各自保存
  s3c_pm_save_gpios();
  s3c_pm_save_uarts();
  s3c_pm_save_core();

//设置外部中断用于唤醒
  s3c_pm_configure_extint();
  S3C_PMDBG("sleep: irq wakeup masks: lx,lxn",
  s3c_irqwake_intmask, s3c_irqwake_eintmask);
  s3c_pm_arch_prepare_irqs();

  pm_cpu_prep();

  flush_cache_all();
  s3c_pm_check_store();

  s3c_pm_arch_stop_clocks();

  s3c_cpu_save(regs_save);

//当接收到一个外部中断时,系统开始恢复
  cpu_init();

  s3c_pm_restore_core();
  s3c_pm_restore_uarts();
  s3c_pm_restore_gpios();
  s3c_pm_debug_init();

  s3c_pm_arch_show_resume_irqs();
  S3C_PMDBG("%s: post sleep, preparing to returnn", __func__);
  s3c_pm_check_restore();

  S3C_PMDBG("S3C PM Resume (post-restore)n");
  return 0;
}