1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12/* Implementation of a logical timer for pc99 platforms 13 * 14 * We try to use the HPET, but if that doesn't work we use the PIT. 15 */ 16#include <platsupport/plat/timer.h> 17#include <platsupport/arch/tsc.h> 18#include <platsupport/pmem.h> 19#include <utils/util.h> 20#include <platsupport/plat/acpi/acpi.h> 21#include <platsupport/plat/hpet.h> 22 23#include "../../ltimer.h" 24 25/* This is duplicated from constants.h in libsel4 for the moment. Interrupt allocation 26 shouldn't be happening here in this driver, until that is fixed this hack is needed */ 27#define IRQ_OFFSET (0x20 + 16) 28 29typedef enum { 30 HPET, 31 PIT 32} pc99_timer_t; 33 34typedef struct { 35 pc99_timer_t type; 36 /* we are either using the HPET or the PIT */ 37 union { 38 struct { 39 hpet_t device; 40 pmem_region_t region; 41 uint64_t period; 42 hpet_config_t config; 43 } hpet; 44 struct { 45 pit_t device; 46 uint32_t freq; 47 /* the PIT can only set short timeouts - if we have 48 * set intermediate irqs we track when the actual timeout is due here */ 49 uint64_t abs_time; 50 } pit; 51 }; 52 ps_irq_t irq; 53 ps_io_ops_t ops; 54 irq_id_t irq_id; 55 ltimer_callback_fn_t user_callback; 56 void *user_callback_token; 57} pc99_ltimer_t; 58 59static size_t get_num_irqs(void *data) 60{ 61 assert(data != NULL); 62 63 /* both PIT and HPET only have one irq */ 64 return 1; 65} 66 67static int get_nth_irq(void *data, size_t n, ps_irq_t *irq) 68{ 69 70 assert(data != NULL); 71 assert(irq != NULL); 72 assert(n == 0); 73 74 pc99_ltimer_t *pc99_ltimer = data; 75 *irq = pc99_ltimer->irq; 76 return 0; 77} 78 79static size_t get_num_pmems(void *data) 80{ 81 assert(data != NULL); 82 pc99_ltimer_t *pc99_ltimer = data; 83 84 return pc99_ltimer->type == HPET ? 1 : 0; 85} 86 87static int hpet_ltimer_get_nth_pmem(void *data, size_t n, pmem_region_t *pmem) 88{ 89 assert(data != NULL); 90 assert(pmem != NULL); 91 assert(n == 0); 92 93 pc99_ltimer_t *pc99_ltimer = data; 94 *pmem = pc99_ltimer->hpet.region; 95 return 0; 96} 97 98static int pit_ltimer_handle_irq(pc99_ltimer_t *pc99_ltimer) 99{ 100 if (!pc99_ltimer->pit.abs_time) { 101 /* nothing to do */ 102 return 0; 103 } 104 105 uint64_t time = tsc_get_time(pc99_ltimer->pit.freq); 106 if (time > pc99_ltimer->pit.abs_time) { 107 /* we're done here */ 108 pc99_ltimer->pit.abs_time = 0; 109 return 0; 110 } 111 112 /* otherwise need to set another irq */ 113 uint64_t ns = MIN(pc99_ltimer->pit.abs_time - time, PIT_MAX_NS); 114 if (ns < PIT_MIN_NS) { 115 return 0; 116 } 117 return pit_set_timeout(&pc99_ltimer->pit.device, ns, false); 118} 119 120static int hpet_ltimer_handle_irq(pc99_ltimer_t *pc99_ltimer) 121{ 122 /* our hpet driver doesn't do periodic timeouts, so emulate them here */ 123 if (pc99_ltimer->hpet.period > 0) { 124 // try a few times to set a timeout. If we continuously get ETIME then we have 125 // no choice but to panic as there is no meaningful error we can return here 126 // that will allow the user to work out what happened and recover 127 // The whole time we are doing this we are of course losing time as we have to keep on 128 // retrying the timeout with a new notion of the current time, but there is nothing 129 // better we can do with this interface 130 int retries = 10; 131 int error; 132 do { 133 error = hpet_set_timeout(&pc99_ltimer->hpet.device, 134 hpet_get_time(&pc99_ltimer->hpet.device) + pc99_ltimer->hpet.period); 135 retries--; 136 } while (error == ETIME && retries > 0); 137 if (error == ETIME) { 138 ZF_LOGF("Failed to reprogram periodic timeout. Unable to continue"); 139 } 140 if (error != 0) { 141 ZF_LOGF("Unexpected error when reprogramming periodic timeout. Unable to continue."); 142 } 143 } 144 return 0; 145} 146 147static void handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) 148{ 149 assert(data != NULL); 150 pc99_ltimer_t *pc99_ltimer = data; 151 152 /* pc99 timer interrupts are edge triggered so acknowledge now */ 153 int UNUSED error = acknowledge_fn(ack_data); 154 assert(!error); 155 156 error = pc99_ltimer->type == PIT ? pit_ltimer_handle_irq(pc99_ltimer) 157 : hpet_ltimer_handle_irq(pc99_ltimer); 158 assert(!error); 159 160 /* the only interrupts we get are from timeout interrupts */ 161 if (pc99_ltimer->user_callback) { 162 pc99_ltimer->user_callback(pc99_ltimer->user_callback_token, LTIMER_TIMEOUT_EVENT); 163 } 164} 165 166static int hpet_ltimer_get_time(void *data, uint64_t *time) 167{ 168 assert(data != NULL); 169 assert(time != NULL); 170 171 pc99_ltimer_t *pc99_ltimer = data; 172 *time = hpet_get_time(&pc99_ltimer->hpet.device); 173 return 0; 174} 175 176static int pit_ltimer_get_time(void *data, uint64_t *time) 177{ 178 pc99_ltimer_t *pc99_ltimer = data; 179 *time = tsc_get_time(pc99_ltimer->pit.freq); 180 return 0; 181} 182 183static int get_resolution(void *data, uint64_t *resolution) 184{ 185 return ENOSYS; 186} 187 188static int hpet_ltimer_set_timeout(void *data, uint64_t ns, timeout_type_t type) 189{ 190 assert(data != NULL); 191 pc99_ltimer_t *pc99_ltimer = data; 192 193 if (type == TIMEOUT_PERIODIC) { 194 pc99_ltimer->hpet.period = ns; 195 } else { 196 pc99_ltimer->hpet.period = 0; 197 } 198 199 if (type != TIMEOUT_ABSOLUTE) { 200 ns += hpet_get_time(&pc99_ltimer->hpet.device); 201 } 202 203 return hpet_set_timeout(&pc99_ltimer->hpet.device, ns); 204} 205 206static int pit_ltimer_set_timeout(void *data, uint64_t ns, timeout_type_t type) 207{ 208 assert(data != NULL); 209 pc99_ltimer_t *pc99_ltimer = data; 210 211 /* we are overriding any existing timeouts */ 212 pc99_ltimer->pit.abs_time = 0; 213 214 uint64_t time = tsc_get_time(pc99_ltimer->pit.freq); 215 switch (type) { 216 case TIMEOUT_RELATIVE: 217 if (ns > PIT_MAX_NS) { 218 pc99_ltimer->pit.abs_time = ns + time; 219 ns = PIT_MAX_NS; 220 } 221 break; 222 case TIMEOUT_ABSOLUTE: 223 if (ns <= time) { 224 return ETIME; 225 } 226 pc99_ltimer->pit.abs_time = ns; 227 ns = MIN(PIT_MAX_NS, ns - time); 228 break; 229 case TIMEOUT_PERIODIC: 230 if (ns > PIT_MAX_NS) { 231 ZF_LOGE("Periodic timeouts %u not implemented for PIT ltimer", (uint32_t) PIT_MAX_NS); 232 return ENOSYS; 233 } 234 break; 235 } 236 237 int error = pit_set_timeout(&pc99_ltimer->pit.device, ns, type == TIMEOUT_PERIODIC); 238 if (error == EINVAL && type == TIMEOUT_ABSOLUTE ) { 239 /* we capped the value we set at the highest value for the PIT, however this 240 * could still have been too small - in this case the absolute timeout has 241 * already passed */ 242 return ETIME; 243 } 244 245 return error; 246} 247 248static int pit_ltimer_reset(void *data) 249{ 250 assert(data != NULL); 251 pc99_ltimer_t *pc99_ltimer = data; 252 pit_cancel_timeout(&pc99_ltimer->pit.device); 253 return 0; 254} 255 256static int hpet_ltimer_reset(void *data) 257{ 258 assert(data != NULL); 259 pc99_ltimer_t *pc99_ltimer = data; 260 261 hpet_stop(&pc99_ltimer->hpet.device); 262 hpet_start(&pc99_ltimer->hpet.device); 263 pc99_ltimer->hpet.period = 0; 264 return 0; 265} 266 267static void destroy(void *data) 268{ 269 assert(data); 270 271 pc99_ltimer_t *pc99_ltimer = data; 272 273 if (pc99_ltimer->type == HPET && pc99_ltimer->hpet.config.vaddr) { 274 hpet_stop(&pc99_ltimer->hpet.device); 275 ps_pmem_unmap(&pc99_ltimer->ops, pc99_ltimer->hpet.region, pc99_ltimer->hpet.config.vaddr); 276 } else { 277 assert(pc99_ltimer->type == PIT); 278 pit_cancel_timeout(&pc99_ltimer->pit.device); 279 } 280 281 if (pc99_ltimer->irq_id > PS_INVALID_IRQ_ID) { 282 ZF_LOGF_IF(ps_irq_unregister(&pc99_ltimer->ops.irq_ops, pc99_ltimer->irq_id), 283 "Failed to clean-up the IRQ ID!"); 284 } 285 286 ps_free(&pc99_ltimer->ops.malloc_ops, sizeof(pc99_ltimer), pc99_ltimer); 287} 288 289static inline int 290ltimer_init_common(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) 291{ 292 pc99_ltimer_t *pc99_ltimer = ltimer->data; 293 pc99_ltimer->ops = ops; 294 pc99_ltimer->user_callback = callback; 295 pc99_ltimer->user_callback_token = callback_token; 296 ltimer->destroy = destroy; 297 298 /* setup the interrupts */ 299 pc99_ltimer->irq_id = ps_irq_register(&ops.irq_ops, pc99_ltimer->irq, handle_irq, 300 pc99_ltimer); 301 if (pc99_ltimer->irq_id < 0) { 302 return EIO; 303 } 304 305 return 0; 306} 307 308static int ltimer_hpet_init_internal(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, 309 void *callback_token) 310{ 311 pc99_ltimer_t *pc99_ltimer = ltimer->data; 312 313 int error = ltimer_init_common(ltimer, ops, callback, callback_token); 314 if (error) { 315 destroy(pc99_ltimer); 316 return -1; 317 } 318 319 /* map in the paddr */ 320 pc99_ltimer->hpet.config.vaddr = ps_pmem_map(&ops, pc99_ltimer->hpet.region, false, PS_MEM_NORMAL); 321 if (pc99_ltimer->hpet.config.vaddr == NULL) { 322 destroy(pc99_ltimer); 323 return -1; 324 } 325 326 ltimer->get_time = hpet_ltimer_get_time; 327 ltimer->get_resolution = get_resolution; 328 ltimer->set_timeout = hpet_ltimer_set_timeout; 329 ltimer->reset = hpet_ltimer_reset; 330 331 /* check if we have requested the IRQ that collides with the PIT. This check is not 332 * particularly robust, as the legacy PIT route does not *have* to live on pin 2 333 * and to be more accurate we should check the ACPI tables instead, but that is 334 * difficult to do here and we shall ignore as an unlikely case */ 335 if (pc99_ltimer->hpet.config.ioapic_delivery && pc99_ltimer->hpet.config.irq == 2) { 336 /* put the PIT into a known state to disable it from counting and genering interrupts */ 337 pit_t temp_pit; 338 /* the pit_init function declares that it may only be called once, we can only hope that 339 * it hasn't been called before and carry on */ 340 error = pit_init(&temp_pit, ops.io_port_ops); 341 if (!error) { 342 error = pit_cancel_timeout(&temp_pit); 343 if (error) { 344 /* if we fail to operate on an initialized pit then assume nothing is sane and abort */ 345 ZF_LOGE("PIT command failed!"); 346 return error; 347 } else { 348 ZF_LOGI("Disabled PIT under belief it was using same interrupt as HPET, and " 349 "this driver does not support interrupt sharing."); 350 } 351 } else { 352 ZF_LOGW("Could not ensure PIT was not counting on pin 2, you may get spurious interrupts"); 353 } 354 } 355 356 error = hpet_init(&pc99_ltimer->hpet.device, pc99_ltimer->hpet.config); 357 if (!error) { 358 error = hpet_start(&pc99_ltimer->hpet.device); 359 } 360 361 return error; 362} 363 364int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) 365{ 366 int error = ltimer_default_describe(ltimer, ops); 367 if (error) { 368 return error; 369 } 370 371 pc99_ltimer_t *pc99_ltimer = ltimer->data; 372 if (pc99_ltimer->type == PIT) { 373 return ltimer_pit_init(ltimer, ops, callback, callback_token); 374 } else { 375 assert(pc99_ltimer->type == HPET); 376 return ltimer_hpet_init_internal(ltimer, ops, callback, callback_token); 377 } 378} 379 380int ltimer_hpet_init(ltimer_t *ltimer, ps_io_ops_t ops, ps_irq_t irq, pmem_region_t region, 381 ltimer_callback_fn_t callback, void *callback_token) 382{ 383 int error = ltimer_hpet_describe(ltimer, ops, irq, region); 384 if (error) { 385 return error; 386 } 387 388 return ltimer_hpet_init_internal(ltimer, ops, callback, callback_token); 389} 390 391int ltimer_pit_init_freq(ltimer_t *ltimer, ps_io_ops_t ops, uint64_t freq, ltimer_callback_fn_t callback, 392 void *callback_token) 393{ 394 int error = ltimer_pit_describe(ltimer, ops); 395 if (error) { 396 return error; 397 } 398 399 pc99_ltimer_t *pc99_ltimer = ltimer->data; 400 401 error = ltimer_init_common(ltimer, ops, callback, callback_token); 402 if (error) { 403 destroy(pc99_ltimer); 404 return error; 405 } 406 407 ltimer->get_time = pit_ltimer_get_time; 408 ltimer->get_resolution = get_resolution; 409 ltimer->set_timeout = pit_ltimer_set_timeout; 410 ltimer->reset = pit_ltimer_reset; 411 pc99_ltimer->pit.freq = freq; 412 return pit_init(&pc99_ltimer->pit.device, ops.io_port_ops); 413} 414 415int ltimer_pit_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) 416{ 417 int error = ltimer_pit_init_freq(ltimer, ops, 0, callback, callback_token); 418 if (error) { 419 return error; 420 } 421 422 /* now calculate the tsc freq */ 423 pc99_ltimer_t *pc99_ltimer = ltimer->data; 424 pc99_ltimer->pit.freq = tsc_calculate_frequency_pit(&pc99_ltimer->pit.device); 425 if (pc99_ltimer->pit.freq == 0) { 426 ltimer_destroy(ltimer); 427 return ENOSYS; 428 } 429 return 0; 430} 431 432uint32_t ltimer_pit_get_tsc_freq(ltimer_t *ltimer) 433{ 434 pc99_ltimer_t *pc99_ltimer = ltimer->data; 435 return pc99_ltimer->pit.freq; 436} 437 438int _ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops, acpi_t *acpi) 439{ 440 pmem_region_t hpet_region; 441 442 int error = (acpi != NULL) ? hpet_parse_acpi(acpi, &hpet_region): 1; 443 444 if (!error) { 445 ps_irq_t irq; 446 error = ltimer_hpet_describe_with_region(ltimer, ops, hpet_region, &irq); 447 } 448 449 if (error) { 450 /* HPET failed - use the pit */ 451 error = ltimer_pit_describe(ltimer, ops); 452 } 453 454 return error; 455} 456 457int ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops) 458{ 459 acpi_t *acpi = acpi_init(ops.io_mapper); 460 return _ltimer_default_describe(ltimer, ops, acpi); 461} 462 463int ltimer_default_describe_with_rsdp(ltimer_t *ltimer, ps_io_ops_t ops, acpi_rsdp_t rsdp) 464{ 465 acpi_t *acpi = acpi_init_with_rsdp(ops.io_mapper, rsdp); 466 return _ltimer_default_describe(ltimer, ops, acpi); 467} 468 469int ltimer_hpet_describe_with_region(ltimer_t *ltimer, ps_io_ops_t ops, pmem_region_t region, ps_irq_t *irq) 470{ 471 /* try to map the HPET to query its properties */ 472 void *vaddr = ps_pmem_map(&ops, region, false, PS_MEM_NORMAL); 473 if (vaddr == NULL) { 474 return ENOSYS; 475 } 476 477 /* first try to use MSIs */ 478 if (hpet_supports_fsb_delivery(vaddr)) { 479 irq->type = PS_MSI; 480 irq->msi.pci_bus = 0; 481 irq->msi.pci_dev = 0; 482 irq->msi.pci_func = 0; 483 irq->msi.handle = 0; 484 irq->msi.vector = DEFAULT_HPET_MSI_VECTOR; 485 } else { 486 /* try a IOAPIC */ 487 irq->type = PS_IOAPIC; 488 irq->ioapic.pin = FFS(hpet_ioapic_irq_delivery_mask(vaddr)) - 1; 489 irq->ioapic.level = hpet_level(vaddr); 490 /* HPET is always active high polarity */ 491 irq->ioapic.polarity = 1; 492 /* HPET always delivers to the first I/O APIC */ 493 irq->ioapic.ioapic = 0; 494 irq->ioapic.vector = 0; /* TODO how to work this out properly */ 495 } 496 497 ps_pmem_unmap(&ops, region, vaddr); 498 return ltimer_hpet_describe(ltimer, ops, *irq, region); 499} 500 501int ltimer_pit_describe(ltimer_t *ltimer, ps_io_ops_t ops) 502{ 503 int error = ps_calloc(&ops.malloc_ops, 1, sizeof(pc99_ltimer_t), <imer->data); 504 if (error) { 505 return error; 506 } 507 508 pc99_ltimer_t *pc99_ltimer = ltimer->data; 509 pc99_ltimer->type = PIT; 510 if (config_set(CONFIG_IRQ_IOAPIC)) { 511 /* Use the IOAPIC if we can */ 512 pc99_ltimer->irq = (ps_irq_t) { .type = PS_IOAPIC, .ioapic = { .ioapic = 0, .pin = PIT_INTERRUPT, 513 .level = 0, .polarity = 0, 514 .vector = PIT_INTERRUPT }}; 515 } else { 516 /* Default to the PIC */ 517 pc99_ltimer->irq = (ps_irq_t) { .type = PS_INTERRUPT, .irq = { .number = PIT_INTERRUPT }}; 518 } 519 pc99_ltimer->irq_id = PS_INVALID_IRQ_ID; 520 ltimer->get_num_irqs = get_num_irqs; 521 ltimer->get_num_pmems = get_num_pmems; 522 ltimer->get_nth_irq = get_nth_irq; 523 return 0; 524} 525 526int ltimer_hpet_describe(ltimer_t *ltimer, ps_io_ops_t ops, ps_irq_t irq, pmem_region_t region) 527{ 528 int error = ps_calloc(&ops.malloc_ops, 1, sizeof(pc99_ltimer_t), <imer->data); 529 if (error) { 530 return error; 531 } 532 533 pc99_ltimer_t *pc99_ltimer = ltimer->data; 534 pc99_ltimer->type = HPET; 535 pc99_ltimer->irq_id = PS_INVALID_IRQ_ID; 536 ltimer->get_num_irqs = get_num_irqs; 537 ltimer->get_nth_irq = get_nth_irq; 538 ltimer->get_num_pmems = get_num_pmems; 539 ltimer->get_nth_pmem = hpet_ltimer_get_nth_pmem; 540 541 pc99_ltimer->hpet.region = region; 542 pc99_ltimer->hpet.config.irq = irq.type == PS_MSI ? irq.msi.vector + IRQ_OFFSET : irq.ioapic.pin; 543 pc99_ltimer->hpet.config.ioapic_delivery = (irq.type == PS_IOAPIC); 544 pc99_ltimer->irq = irq; 545 546 return 0; 547} 548