diff options
author | K. Y. Srinivasan <kys@microsoft.com> | 2015-01-10 02:54:32 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-01-25 12:17:57 -0500 |
commit | 4061ed9e2aaac31daef44f06e9b83143c78b24b2 (patch) | |
tree | 26043c2e194e4e796fdbcbf95770aa7f74172a68 | |
parent | ab3de22bb4a3d4bda2d0ec8bebcb76a40f1cbf9b (diff) |
Drivers: hv: vmbus: Implement a clockevent device
Implement a clockevent device based on the timer support available on
Hyper-V.
In this version of the patch I have addressed Jason's review comments.
Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Reviewed-by: Jason Wang <jasowang@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | arch/x86/include/uapi/asm/hyperv.h | 11 | ||||
-rw-r--r-- | drivers/hv/hv.c | 78 | ||||
-rw-r--r-- | drivers/hv/hyperv_vmbus.h | 21 | ||||
-rw-r--r-- | drivers/hv/vmbus_drv.c | 37 |
4 files changed, 145 insertions, 2 deletions
diff --git a/arch/x86/include/uapi/asm/hyperv.h b/arch/x86/include/uapi/asm/hyperv.h index 462efe746d77..90c458e66e13 100644 --- a/arch/x86/include/uapi/asm/hyperv.h +++ b/arch/x86/include/uapi/asm/hyperv.h | |||
@@ -187,6 +187,17 @@ | |||
187 | #define HV_X64_MSR_SINT14 0x4000009E | 187 | #define HV_X64_MSR_SINT14 0x4000009E |
188 | #define HV_X64_MSR_SINT15 0x4000009F | 188 | #define HV_X64_MSR_SINT15 0x4000009F |
189 | 189 | ||
190 | /* | ||
191 | * Synthetic Timer MSRs. Four timers per vcpu. | ||
192 | */ | ||
193 | #define HV_X64_MSR_STIMER0_CONFIG 0x400000B0 | ||
194 | #define HV_X64_MSR_STIMER0_COUNT 0x400000B1 | ||
195 | #define HV_X64_MSR_STIMER1_CONFIG 0x400000B2 | ||
196 | #define HV_X64_MSR_STIMER1_COUNT 0x400000B3 | ||
197 | #define HV_X64_MSR_STIMER2_CONFIG 0x400000B4 | ||
198 | #define HV_X64_MSR_STIMER2_COUNT 0x400000B5 | ||
199 | #define HV_X64_MSR_STIMER3_CONFIG 0x400000B6 | ||
200 | #define HV_X64_MSR_STIMER3_COUNT 0x400000B7 | ||
190 | 201 | ||
191 | #define HV_X64_MSR_HYPERCALL_ENABLE 0x00000001 | 202 | #define HV_X64_MSR_HYPERCALL_ENABLE 0x00000001 |
192 | #define HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_SHIFT 12 | 203 | #define HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_SHIFT 12 |
diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 3e4235c7a47f..50e51a51ff8b 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c | |||
@@ -28,7 +28,9 @@ | |||
28 | #include <linux/hyperv.h> | 28 | #include <linux/hyperv.h> |
29 | #include <linux/version.h> | 29 | #include <linux/version.h> |
30 | #include <linux/interrupt.h> | 30 | #include <linux/interrupt.h> |
31 | #include <linux/clockchips.h> | ||
31 | #include <asm/hyperv.h> | 32 | #include <asm/hyperv.h> |
33 | #include <asm/mshyperv.h> | ||
32 | #include "hyperv_vmbus.h" | 34 | #include "hyperv_vmbus.h" |
33 | 35 | ||
34 | /* The one and only */ | 36 | /* The one and only */ |
@@ -37,6 +39,10 @@ struct hv_context hv_context = { | |||
37 | .hypercall_page = NULL, | 39 | .hypercall_page = NULL, |
38 | }; | 40 | }; |
39 | 41 | ||
42 | #define HV_TIMER_FREQUENCY (10 * 1000 * 1000) /* 100ns period */ | ||
43 | #define HV_MAX_MAX_DELTA_TICKS 0xffffffff | ||
44 | #define HV_MIN_DELTA_TICKS 1 | ||
45 | |||
40 | /* | 46 | /* |
41 | * query_hypervisor_info - Get version info of the windows hypervisor | 47 | * query_hypervisor_info - Get version info of the windows hypervisor |
42 | */ | 48 | */ |
@@ -144,6 +150,8 @@ int hv_init(void) | |||
144 | sizeof(int) * NR_CPUS); | 150 | sizeof(int) * NR_CPUS); |
145 | memset(hv_context.event_dpc, 0, | 151 | memset(hv_context.event_dpc, 0, |
146 | sizeof(void *) * NR_CPUS); | 152 | sizeof(void *) * NR_CPUS); |
153 | memset(hv_context.clk_evt, 0, | ||
154 | sizeof(void *) * NR_CPUS); | ||
147 | 155 | ||
148 | max_leaf = query_hypervisor_info(); | 156 | max_leaf = query_hypervisor_info(); |
149 | 157 | ||
@@ -258,10 +266,63 @@ u16 hv_signal_event(void *con_id) | |||
258 | return status; | 266 | return status; |
259 | } | 267 | } |
260 | 268 | ||
269 | static int hv_ce_set_next_event(unsigned long delta, | ||
270 | struct clock_event_device *evt) | ||
271 | { | ||
272 | cycle_t current_tick; | ||
273 | |||
274 | WARN_ON(evt->mode != CLOCK_EVT_MODE_ONESHOT); | ||
275 | |||
276 | rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick); | ||
277 | current_tick += delta; | ||
278 | wrmsrl(HV_X64_MSR_STIMER0_COUNT, current_tick); | ||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | static void hv_ce_setmode(enum clock_event_mode mode, | ||
283 | struct clock_event_device *evt) | ||
284 | { | ||
285 | union hv_timer_config timer_cfg; | ||
286 | |||
287 | switch (mode) { | ||
288 | case CLOCK_EVT_MODE_PERIODIC: | ||
289 | /* unsupported */ | ||
290 | break; | ||
291 | |||
292 | case CLOCK_EVT_MODE_ONESHOT: | ||
293 | timer_cfg.enable = 1; | ||
294 | timer_cfg.auto_enable = 1; | ||
295 | timer_cfg.sintx = VMBUS_MESSAGE_SINT; | ||
296 | wrmsrl(HV_X64_MSR_STIMER0_CONFIG, timer_cfg.as_uint64); | ||
297 | break; | ||
298 | |||
299 | case CLOCK_EVT_MODE_UNUSED: | ||
300 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
301 | wrmsrl(HV_X64_MSR_STIMER0_COUNT, 0); | ||
302 | wrmsrl(HV_X64_MSR_STIMER0_CONFIG, 0); | ||
303 | break; | ||
304 | case CLOCK_EVT_MODE_RESUME: | ||
305 | break; | ||
306 | } | ||
307 | } | ||
308 | |||
309 | static void hv_init_clockevent_device(struct clock_event_device *dev, int cpu) | ||
310 | { | ||
311 | dev->name = "Hyper-V clockevent"; | ||
312 | dev->features = CLOCK_EVT_FEAT_ONESHOT; | ||
313 | dev->cpumask = cpumask_of(cpu); | ||
314 | dev->rating = 1000; | ||
315 | dev->owner = THIS_MODULE; | ||
316 | |||
317 | dev->set_mode = hv_ce_setmode; | ||
318 | dev->set_next_event = hv_ce_set_next_event; | ||
319 | } | ||
320 | |||
261 | 321 | ||
262 | int hv_synic_alloc(void) | 322 | int hv_synic_alloc(void) |
263 | { | 323 | { |
264 | size_t size = sizeof(struct tasklet_struct); | 324 | size_t size = sizeof(struct tasklet_struct); |
325 | size_t ced_size = sizeof(struct clock_event_device); | ||
265 | int cpu; | 326 | int cpu; |
266 | 327 | ||
267 | for_each_online_cpu(cpu) { | 328 | for_each_online_cpu(cpu) { |
@@ -272,6 +333,13 @@ int hv_synic_alloc(void) | |||
272 | } | 333 | } |
273 | tasklet_init(hv_context.event_dpc[cpu], vmbus_on_event, cpu); | 334 | tasklet_init(hv_context.event_dpc[cpu], vmbus_on_event, cpu); |
274 | 335 | ||
336 | hv_context.clk_evt[cpu] = kzalloc(ced_size, GFP_ATOMIC); | ||
337 | if (hv_context.clk_evt[cpu] == NULL) { | ||
338 | pr_err("Unable to allocate clock event device\n"); | ||
339 | goto err; | ||
340 | } | ||
341 | hv_init_clockevent_device(hv_context.clk_evt[cpu], cpu); | ||
342 | |||
275 | hv_context.synic_message_page[cpu] = | 343 | hv_context.synic_message_page[cpu] = |
276 | (void *)get_zeroed_page(GFP_ATOMIC); | 344 | (void *)get_zeroed_page(GFP_ATOMIC); |
277 | 345 | ||
@@ -305,6 +373,7 @@ err: | |||
305 | static void hv_synic_free_cpu(int cpu) | 373 | static void hv_synic_free_cpu(int cpu) |
306 | { | 374 | { |
307 | kfree(hv_context.event_dpc[cpu]); | 375 | kfree(hv_context.event_dpc[cpu]); |
376 | kfree(hv_context.clk_evt[cpu]); | ||
308 | if (hv_context.synic_event_page[cpu]) | 377 | if (hv_context.synic_event_page[cpu]) |
309 | free_page((unsigned long)hv_context.synic_event_page[cpu]); | 378 | free_page((unsigned long)hv_context.synic_event_page[cpu]); |
310 | if (hv_context.synic_message_page[cpu]) | 379 | if (hv_context.synic_message_page[cpu]) |
@@ -388,6 +457,15 @@ void hv_synic_init(void *arg) | |||
388 | hv_context.vp_index[cpu] = (u32)vp_index; | 457 | hv_context.vp_index[cpu] = (u32)vp_index; |
389 | 458 | ||
390 | INIT_LIST_HEAD(&hv_context.percpu_list[cpu]); | 459 | INIT_LIST_HEAD(&hv_context.percpu_list[cpu]); |
460 | |||
461 | /* | ||
462 | * Register the per-cpu clockevent source. | ||
463 | */ | ||
464 | if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE) | ||
465 | clockevents_config_and_register(hv_context.clk_evt[cpu], | ||
466 | HV_TIMER_FREQUENCY, | ||
467 | HV_MIN_DELTA_TICKS, | ||
468 | HV_MAX_MAX_DELTA_TICKS); | ||
391 | return; | 469 | return; |
392 | } | 470 | } |
393 | 471 | ||
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index c386d8dc7223..44b1c9424712 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h | |||
@@ -178,6 +178,23 @@ struct hv_message_header { | |||
178 | }; | 178 | }; |
179 | }; | 179 | }; |
180 | 180 | ||
181 | /* | ||
182 | * Timer configuration register. | ||
183 | */ | ||
184 | union hv_timer_config { | ||
185 | u64 as_uint64; | ||
186 | struct { | ||
187 | u64 enable:1; | ||
188 | u64 periodic:1; | ||
189 | u64 lazy:1; | ||
190 | u64 auto_enable:1; | ||
191 | u64 reserved_z0:12; | ||
192 | u64 sintx:4; | ||
193 | u64 reserved_z1:44; | ||
194 | }; | ||
195 | }; | ||
196 | |||
197 | |||
181 | /* Define timer message payload structure. */ | 198 | /* Define timer message payload structure. */ |
182 | struct hv_timer_message_payload { | 199 | struct hv_timer_message_payload { |
183 | u32 timer_index; | 200 | u32 timer_index; |
@@ -519,6 +536,10 @@ struct hv_context { | |||
519 | * buffer to post messages to the host. | 536 | * buffer to post messages to the host. |
520 | */ | 537 | */ |
521 | void *post_msg_page[NR_CPUS]; | 538 | void *post_msg_page[NR_CPUS]; |
539 | /* | ||
540 | * Support PV clockevent device. | ||
541 | */ | ||
542 | struct clock_event_device *clk_evt[NR_CPUS]; | ||
522 | }; | 543 | }; |
523 | 544 | ||
524 | extern struct hv_context hv_context; | 545 | extern struct hv_context hv_context; |
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 4d6b26979fbd..7488111ec057 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c | |||
@@ -32,6 +32,7 @@ | |||
32 | #include <linux/completion.h> | 32 | #include <linux/completion.h> |
33 | #include <linux/hyperv.h> | 33 | #include <linux/hyperv.h> |
34 | #include <linux/kernel_stat.h> | 34 | #include <linux/kernel_stat.h> |
35 | #include <linux/clockchips.h> | ||
35 | #include <asm/hyperv.h> | 36 | #include <asm/hyperv.h> |
36 | #include <asm/hypervisor.h> | 37 | #include <asm/hypervisor.h> |
37 | #include <asm/mshyperv.h> | 38 | #include <asm/mshyperv.h> |
@@ -578,6 +579,34 @@ static void vmbus_onmessage_work(struct work_struct *work) | |||
578 | kfree(ctx); | 579 | kfree(ctx); |
579 | } | 580 | } |
580 | 581 | ||
582 | void hv_process_timer_expiration(struct hv_message *msg, int cpu) | ||
583 | { | ||
584 | struct clock_event_device *dev = hv_context.clk_evt[cpu]; | ||
585 | |||
586 | if (dev->event_handler) | ||
587 | dev->event_handler(dev); | ||
588 | |||
589 | msg->header.message_type = HVMSG_NONE; | ||
590 | |||
591 | /* | ||
592 | * Make sure the write to MessageType (ie set to | ||
593 | * HVMSG_NONE) happens before we read the | ||
594 | * MessagePending and EOMing. Otherwise, the EOMing | ||
595 | * will not deliver any more messages since there is | ||
596 | * no empty slot | ||
597 | */ | ||
598 | mb(); | ||
599 | |||
600 | if (msg->header.message_flags.msg_pending) { | ||
601 | /* | ||
602 | * This will cause message queue rescan to | ||
603 | * possibly deliver another msg from the | ||
604 | * hypervisor | ||
605 | */ | ||
606 | wrmsrl(HV_X64_MSR_EOM, 0); | ||
607 | } | ||
608 | } | ||
609 | |||
581 | static void vmbus_on_msg_dpc(unsigned long data) | 610 | static void vmbus_on_msg_dpc(unsigned long data) |
582 | { | 611 | { |
583 | int cpu = smp_processor_id(); | 612 | int cpu = smp_processor_id(); |
@@ -667,8 +696,12 @@ static void vmbus_isr(void) | |||
667 | msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; | 696 | msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; |
668 | 697 | ||
669 | /* Check if there are actual msgs to be processed */ | 698 | /* Check if there are actual msgs to be processed */ |
670 | if (msg->header.message_type != HVMSG_NONE) | 699 | if (msg->header.message_type != HVMSG_NONE) { |
671 | tasklet_schedule(&msg_dpc); | 700 | if (msg->header.message_type == HVMSG_TIMER_EXPIRED) |
701 | hv_process_timer_expiration(msg, cpu); | ||
702 | else | ||
703 | tasklet_schedule(&msg_dpc); | ||
704 | } | ||
672 | } | 705 | } |
673 | 706 | ||
674 | /* | 707 | /* |