時間子係統12_clockevent設備注冊
// 向係統注冊時鍾事件設備 // 函數任務: // 1.添加設備到clockevent_devices鏈表 // 2.通知tick device管理機製有clockevent設備注冊 1.1 void clockevents_register_device(struct clock_event_device *dev) { unsigned long flags; raw_spin_lock_irqsave(&clockevents_lock, flags); //將設備添加到clockevent_devices鏈表 list_add(&dev->list, &clockevent_devices); //向係統通知有新設備注冊 clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev); raw_spin_unlock_irqrestore(&clockevents_lock, flags); } // clockevent設備注冊處理函數 // 調用路徑:tick_notify->CLOCK_EVT_NOTIFY_ADD // 函數任務: // 1.確認設備服務於本cpu // 2.如果設備非本cpu的local device // 2.1 設置cpu的irq親和性 // 2.2 如果本cpu已經綁定一個local device,並且二者服務的cpu相同,則忽略 // 3.如果本cpu已有local device,選擇兩者中更好的 // 3.1 優先選擇支持單觸發的 // 3.2 然後選擇高rating的 // 4.如果本cpu使用的clockevent設備為廣播設備,直接關閉 // 5.更新cpu的tick device為新tick device // 6.啟動tick device // 7.異步通知周期時鍾關於時鍾事件設備的改變 // 注:如果不能設置為本cpu tick device的事件源,考慮其是否可以作為廣播設備。 1.2 static int tick_check_new_device(struct clock_event_device *newdev) { struct clock_event_device *curdev; struct tick_device *td; int cpu, ret = NOTIFY_OK; unsigned long flags; raw_spin_lock_irqsave(&tick_device_lock, flags); //檢查設備是否服務於本cpu cpu = smp_processor_id(); if (!cpumask_test_cpu(cpu, newdev->cpumask)) goto out_bc; //當前cpu的tick_device td = &per_cpu(tick_cpu_device, cpu); curdev = td->evtdev; //檢查是否為本cpu的local device // 如果設備隻服務於本cpu,則為local device if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) { //非local device,並且不能設置irq 親和性,忽略 if (!irq_can_set_affinity(newdev->irq)) goto out_bc; //如果本cpu已經綁定有一個local device,則忽略 if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu))) goto out_bc; } //本cpu已有一個tick device if (curdev) { //選擇兩者中最好的tick device //優先選擇 支持單觸發模式 if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) && !(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) goto out_bc; //然後選擇 高rating if (curdev->rating >= newdev->rating) goto out_bc; } //如果當前使用的clockevent設備為廣播設備,直接關閉 if (tick_is_broadcast_device(curdev)) { clockevents_shutdown(curdev); curdev = NULL; } //設置cpu的tick device為新tick device clockevents_exchange_device(curdev, newdev); //啟動tick device tick_setup_device(td, newdev, cpu, cpumask_of(cpu)); //異步通知周期時鍾關於時鍾事件設備的改變 if (newdev->features & CLOCK_EVT_FEAT_ONESHOT) tick_oneshot_notify(); raw_spin_unlock_irqrestore(&tick_device_lock, flags); return NOTIFY_STOP; out_bc: //嚐試設置clockevent設備為tick device if (tick_check_broadcast_device(newdev)) ret = NOTIFY_STOP; raw_spin_unlock_irqrestore(&tick_device_lock, flags); return ret; } // 綁定tick device到clockevnet設備 // 函數任務: // 1.tick device未綁定過clockevent // 1.1 選擇cpu負責更新全局時間 // 1.2 以周期模式啟動 // 2.tick device綁定過clockevnet // 2.1 暫時設置其eventhandler=noop,保留下次到期時間 // 3.綁定tick device到新clockevent // 4.clockevent並不僅服務於本cpu,設置irq親和本cpu // 5.如果設備沒有正常工作,將設備加入廣播組,然後退出 // 6.否則更新clockevent的事件處理函數 // 6.1 首次綁定,以周期模式啟動 // 6.2 否則按照之前的模式啟動 // 調用路徑:tick_check_new_device->tick_setup_device // 注: // 1.由負責更新全局時間的cpu,初始化tick_next_period,tick_period // tick_next_period, 周期時鍾下次到期的時間 // tick_period,周期時鍾的周期,1HZ經曆的納秒 1.2 static void tick_setup_device(struct tick_device *td, struct clock_event_device *newdev, int cpu, const struct cpumask *cpumask) { ktime_t next_event; void (*handler)(struct clock_event_device *) = NULL; //tick device第一次創建,沒有綁定clockevent if (!td->evtdev) { //如果沒有cpu負責do_timer,由本cpu負責 if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) { tick_do_timer_cpu = cpu; tick_next_period = ktime_get(); tick_period = ktime_set(0, NSEC_PER_SEC / HZ); } //以周期模式啟動 td->mode = TICKDEV_MODE_PERIODIC; } else { //tick device非第一次創建,此次為更新clockevent //更新event處理函數為noop handler = td->evtdev->event_handler; next_event = td->evtdev->next_event; td->evtdev->event_handler = clockevents_handle_noop; } //更新tick device的clockevent td->evtdev = newdev; //clockevent並不僅服務於本cpu,設置irq與本cpu親和 if (!cpumask_equal(newdev->cpumask, cpumask)) irq_set_affinity(newdev->irq, cpumask); //如果設備沒有正常工作,將設備加入廣播組,然後退出 if (tick_device_uses_broadcast(newdev, cpu)) return; //設置clockevent的eventhandler if (td->mode == TICKDEV_MODE_PERIODIC) { //以周期模式啟動 tick_setup_periodic(newdev, 0); } else { //以單觸發模式啟動 tick_setup_oneshot(newdev, handler, next_event); } } // 周期模式啟動設備 // 調用路徑:tick_check_new_device->tick_setup_device->tick_setup_periodic // 函數任務: // 1.設置設備周期處理函數 // 2.根據設備支持的模式,激活設備 // 2.1 如果設備支持周期模式,並且為激活單觸發模式 // 2.1.1 設置設備當前為周期模式,退出 // 2.1.2 由周期處理函數執行周期任務 // 2.2 否則,通過單觸發模式模擬周期運行 // 2.2.1 獲取下次tick到期的時間 // 2.2.2 編程設備下一次到期時間 // 2.2.3 由周期處理函數執行周期任務,更新下次到期時間 1.3 void tick_setup_periodic(struct clock_event_device *dev, int broadcast) { //設置clockevent的周期處理函數 tick_set_periodic_handler(dev, broadcast); //設備不能正常啟動,則有可能broadcast代其運行 if (!tick_device_is_functional(dev)) return; //如果設備支持周期模式,並且沒有激活單觸發模式 if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) && !tick_broadcast_oneshot_active()) { //設置設備為周期模式 clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC); } else { unsigned long seq; ktime_t next; //獲取下次tick到期的時間 do { seq = read_seqbegin(&xtime_lock); next = tick_next_period; } while (read_seqretry(&xtime_lock, seq)); //設置設備為單觸發模式 clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT); //設置設備下一次到期時間 for (;;) { if (!clockevents_program_event(dev, next, ktime_get())) return; next = ktime_add(next, tick_period); } } } // 單觸發模式啟動設備 // 調用路徑:tick_check_new_device->tick_setup_device->tick_setup_oneshot // 函數任務: // 1.設置事件處理函數 // 2.設置設備當前為單觸發模式 // 3.更新設備事件的到期時間 1.4 void tick_setup_oneshot(struct clock_event_device *newdev, void (*handler)(struct clock_event_device *), ktime_t next_event) { //設置事件處理函數 newdev->event_handler = handler; clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT); //更新設備事件到期時間 tick_dev_program_event(newdev, next_event, 1); } // 設置設備為廣播設備 // 函數任務: // 1.成為廣播設備的條件 // 1.1 設備不受電源狀態的影響 // 1.2 設備的rating高於當前使用的廣播設備 // 2.綁定此設備到全局廣播設備 // 3.如果有設備被代理 // 3.1 開啟廣播設備 2.1 int tick_check_broadcast_device(struct clock_event_device *dev) { //檢查是否可以作為廣播設備 if ((tick_broadcast_device.evtdev && tick_broadcast_device.evtdev->rating >= dev->rating) || (dev->features & CLOCK_EVT_FEAT_C3STOP)) return 0; clockevents_exchange_device(NULL, dev); tick_broadcast_device.evtdev = dev; //有設備需要被廣播設備代理 if (!cpumask_empty(tick_get_broadcast_mask())) { //啟動廣播設備 tick_broadcast_start_periodic(dev); } return 1; } // 開啟廣播設備 // 函數任務: // 1.設置廣播設備的事件處理函數 // 2.編程啟動廣播設備 // 調用路徑:tick_check_new_device->tick_broadcast_start_periodic 2.2 static void tick_broadcast_start_periodic(struct clock_event_device *bc) { if (bc) tick_setup_periodic(bc, 1); } // 將cpu加入廣播組 // 函數任務: // 1.如果cpu的tick device沒有正常工作 // 1.1 設置tick device的事件處理函數為周期事件處理函數 // 1.2 將設備加入廣播組 // 1.3 啟動廣播設備 // 2.否則,如果設備不受省電模式C3的影響 // 2.1 將cpu從廣播組中刪除 3.1 int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu) { unsigned long flags; int ret = 0; raw_spin_lock_irqsave(&tick_broadcast_lock, flags); if (!tick_device_is_functional(dev)) { dev->event_handler = tick_handle_periodic; cpumask_set_cpu(cpu, tick_get_broadcast_mask()); tick_broadcast_start_periodic(tick_broadcast_device.evtdev); ret = 1; } else { //如果本設備不受省電模式C3的影響 if (!(dev->features & CLOCK_EVT_FEAT_C3STOP)) { int cpu = smp_processor_id(); //將cpu從廣播組中刪除 cpumask_clear_cpu(cpu, tick_get_broadcast_mask()); tick_broadcast_clear_oneshot(cpu); } } raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags); return ret; }
最後更新:2017-04-03 14:54:38