Serenity Operating System
1/*
2 * Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/StringView.h>
28#include <Kernel/ACPI/Parser.h>
29#include <Kernel/Interrupts/InterruptManagement.h>
30#include <Kernel/Time/HPET.h>
31#include <Kernel/Time/HPETComparator.h>
32#include <Kernel/Time/TimeManagement.h>
33#include <Kernel/VM/MemoryManager.h>
34
35namespace Kernel {
36
37#define ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD 0x05F5E100
38#define NANOSECOND_PERIOD_TO_HERTZ(x) 1000000000 / x
39#define MEGAHERTZ_TO_HERTZ(x) (x / 1000000)
40
41//#define HPET_DEBUG
42
43namespace HPETFlags {
44enum class Attributes {
45 Counter64BitCapable = 1 << 13,
46 LegacyReplacementRouteCapable = 1 << 15
47};
48
49enum class Configuration {
50 Enable = 0x1,
51 LegacyReplacementRoute = 0x2
52};
53
54enum class TimerConfiguration : u32 {
55 InterruptType = 1 << 1,
56 InterruptEnable = 1 << 2,
57 TimerType = 1 << 3,
58 PeriodicInterruptCapable = 1 << 4,
59 Timer64BitsCapable = 1 << 5,
60 ValueSet = 1 << 6,
61 Force32BitMode = 1 << 7,
62 FSBInterruptEnable = 1 << 14,
63 FSBInterruptDelivery = 1 << 15
64};
65};
66
67struct [[gnu::packed]] TimerStructure
68{
69 u64 configuration_and_capability;
70 u64 comparator_value;
71 u64 fsb_interrupt_route;
72 u64 reserved;
73};
74
75struct [[gnu::packed]] HPETCapabilityRegister
76{
77 u32 attributes; // Note: We must do a 32 bit access to offsets 0x0, or 0x4 only, according to HPET spec.
78 u32 main_counter_tick_period;
79 u64 reserved;
80};
81
82struct [[gnu::packed]] HPETRegister
83{
84 u64 reg;
85 u64 reserved;
86};
87
88struct [[gnu::packed]] HPETRegistersBlock
89{
90 union {
91 volatile HPETCapabilityRegister capabilities;
92 volatile HPETRegister raw_capabilites;
93 };
94 HPETRegister configuration;
95 HPETRegister interrupt_status;
96 u8 reserved[0xF0 - 48];
97 HPETRegister main_counter_value;
98 TimerStructure timers[3];
99 u8 reserved2[0x400 - 0x160];
100};
101
102static HPET* s_hpet;
103static bool hpet_initialized { false };
104
105bool HPET::initialized()
106{
107 return hpet_initialized;
108}
109
110HPET& HPET::the()
111{
112 ASSERT(HPET::initialized());
113 ASSERT(s_hpet != nullptr);
114 return *s_hpet;
115}
116
117bool HPET::test_and_initialize()
118{
119 ASSERT(!HPET::initialized());
120 hpet_initialized = true;
121 auto hpet = ACPI::Parser::the()->find_table("HPET");
122 if (hpet.is_null())
123 return false;
124 klog() << "HPET @ " << hpet;
125
126 auto region = MM.allocate_kernel_region(hpet.page_base(), (PAGE_SIZE * 2), "HPET Initialization", Region::Access::Read);
127 auto* sdt = (volatile ACPI::Structures::HPET*)region->vaddr().offset(hpet.offset_in_page()).as_ptr();
128
129 // Note: HPET is only usable from System Memory
130 ASSERT(sdt->event_timer_block.address_space == (u8)ACPI::GenericAddressStructure::AddressSpace::SystemMemory);
131
132 if (TimeManagement::is_hpet_periodic_mode_allowed()) {
133 if (!check_for_exisiting_periodic_timers()) {
134 dbg() << "HPET: No periodic capable timers";
135 return false;
136 }
137 }
138 s_hpet = new HPET(PhysicalAddress(hpet));
139 return true;
140}
141
142bool HPET::check_for_exisiting_periodic_timers()
143{
144 auto hpet = ACPI::Parser::the()->find_table("HPET");
145 if (hpet.is_null())
146 return false;
147 auto region = MM.allocate_kernel_region(hpet.page_base(), (PAGE_SIZE * 2), "HPET Initialization", Region::Access::Read);
148 auto* sdt = (volatile ACPI::Structures::HPET*)region->vaddr().offset(hpet.offset_in_page()).as_ptr();
149
150 ASSERT(sdt->event_timer_block.address_space == 0);
151
152 auto p_block = PhysicalAddress(sdt->event_timer_block.address);
153 auto block_region = MM.allocate_kernel_region(p_block, (PAGE_SIZE * 2), "HPET Initialization", Region::Access::Read);
154 auto* registers = (volatile HPETRegistersBlock*)block_region->vaddr().offset(p_block.offset_in_page()).as_ptr();
155
156 size_t timers_count = ((registers->raw_capabilites.reg >> 8) & 0x1f) + 1;
157 for (size_t index = 0; index < timers_count; index++) {
158 if (registers->timers[index].configuration_and_capability & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable)
159 return true;
160 }
161 return false;
162}
163
164const FixedArray<RefPtr<HPETComparator>>& HPET::comparators() const
165{
166 return m_comparators;
167}
168
169void HPET::global_disable()
170{
171 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
172 registers_block->configuration.reg &= ~(u32)HPETFlags::Configuration::Enable;
173}
174void HPET::global_enable()
175{
176 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
177 registers_block->configuration.reg |= (u32)HPETFlags::Configuration::Enable;
178}
179
180void HPET::set_periodic_comparator_value(const HPETComparator& comparator, u64 value)
181{
182 disable(comparator);
183 ASSERT(comparator.is_periodic());
184 ASSERT(comparator.comparator_number() <= m_comparators.size());
185 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
186 registers_block->timers[comparator.comparator_number()].configuration_and_capability |= (u32)HPETFlags::TimerConfiguration::ValueSet;
187 registers_block->timers[comparator.comparator_number()].comparator_value = value;
188 enable(comparator);
189}
190
191void HPET::set_non_periodic_comparator_value(const HPETComparator& comparator, u64 value)
192{
193 ASSERT_INTERRUPTS_DISABLED();
194 ASSERT(!comparator.is_periodic());
195 ASSERT(comparator.comparator_number() <= m_comparators.size());
196 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
197 registers_block->timers[comparator.comparator_number()].comparator_value = main_counter_value() + value;
198}
199void HPET::enable_periodic_interrupt(const HPETComparator& comparator)
200{
201#ifdef HPET_DEBUG
202 klog() << "HPET: Set comparator " << comparator.comparator_number() << " to be periodic.";
203#endif
204 disable(comparator);
205 ASSERT(comparator.comparator_number() <= m_comparators.size());
206 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
207 ASSERT(registers_block->timers[comparator.comparator_number()].configuration_and_capability & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable);
208 registers_block->timers[comparator.comparator_number()].configuration_and_capability |= (u32)HPETFlags::TimerConfiguration::TimerType;
209 enable(comparator);
210}
211void HPET::disable_periodic_interrupt(const HPETComparator& comparator)
212{
213#ifdef HPET_DEBUG
214 klog() << "HPET: Disable periodic interrupt in comparator " << comparator.comparator_number() << ".";
215#endif
216 disable(comparator);
217 ASSERT(comparator.comparator_number() <= m_comparators.size());
218 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
219 ASSERT(registers_block->timers[comparator.comparator_number()].configuration_and_capability & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable);
220 registers_block->timers[comparator.comparator_number()].configuration_and_capability &= ~(u32)HPETFlags::TimerConfiguration::TimerType;
221 enable(comparator);
222}
223
224void HPET::disable(const HPETComparator& comparator)
225{
226#ifdef HPET_DEBUG
227 klog() << "HPET: Disable comparator " << comparator.comparator_number() << ".";
228#endif
229 ASSERT(comparator.comparator_number() <= m_comparators.size());
230 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
231 registers_block->timers[comparator.comparator_number()].configuration_and_capability &= ~(u32)HPETFlags::TimerConfiguration::InterruptEnable;
232}
233void HPET::enable(const HPETComparator& comparator)
234{
235#ifdef HPET_DEBUG
236 klog() << "HPET: Enable comparator " << comparator.comparator_number() << ".";
237#endif
238 ASSERT(comparator.comparator_number() <= m_comparators.size());
239 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
240 registers_block->timers[comparator.comparator_number()].configuration_and_capability |= (u32)HPETFlags::TimerConfiguration::InterruptEnable;
241}
242
243u64 HPET::main_counter_value() const
244{
245 auto* registers_block = (const volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
246 return registers_block->main_counter_value.reg;
247}
248u64 HPET::frequency() const
249{
250 return m_frequency;
251}
252
253Vector<unsigned> HPET::capable_interrupt_numbers(const HPETComparator& comparator)
254{
255 ASSERT(comparator.comparator_number() <= m_comparators.size());
256 Vector<unsigned> capable_interrupts;
257 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
258 auto* comparator_registers = (const volatile TimerStructure*)®isters_block->timers[comparator.comparator_number()];
259 u32 interrupt_bitfield = comparator_registers->configuration_and_capability >> 32;
260 for (size_t index = 0; index < 32; index++) {
261 if (interrupt_bitfield & 1)
262 capable_interrupts.append(index);
263 interrupt_bitfield >>= 1;
264 }
265 return capable_interrupts;
266}
267
268Vector<unsigned> HPET::capable_interrupt_numbers(u8 comparator_number)
269{
270 ASSERT(comparator_number <= m_comparators.size());
271 Vector<unsigned> capable_interrupts;
272 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
273 auto* comparator_registers = (const volatile TimerStructure*)®isters_block->timers[comparator_number];
274 u32 interrupt_bitfield = comparator_registers->configuration_and_capability >> 32;
275 for (size_t index = 0; index < 32; index++) {
276 if (interrupt_bitfield & 1)
277 capable_interrupts.append(index);
278 interrupt_bitfield >>= 1;
279 }
280 return capable_interrupts;
281}
282
283void HPET::set_comparator_irq_vector(u8 comparator_number, u8 irq_vector)
284{
285 ASSERT(comparator_number <= m_comparators.size());
286 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
287 auto* comparator_registers = (volatile TimerStructure*)®isters_block->timers[comparator_number];
288 comparator_registers->configuration_and_capability |= (irq_vector << 9);
289}
290
291bool HPET::is_periodic_capable(u8 comparator_number) const
292{
293 ASSERT(comparator_number <= m_comparators.size());
294 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
295 auto* comparator_registers = (const volatile TimerStructure*)®isters_block->timers[comparator_number];
296 return comparator_registers->configuration_and_capability & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable;
297}
298
299void HPET::set_comparators_to_optimal_interrupt_state(size_t)
300{
301 // FIXME: Implement this method for allowing to use HPET timers 2-31...
302 ASSERT_NOT_REACHED();
303}
304
305PhysicalAddress HPET::find_acpi_hept_registers_block()
306{
307 auto region = MM.allocate_kernel_region(m_physical_acpi_hpet_table.page_base(), (PAGE_SIZE * 2), "HPET Initialization", Region::Access::Read);
308 auto* sdt = (ACPI::Structures::HPET*)region->vaddr().offset(m_physical_acpi_hpet_table.offset_in_page()).as_ptr();
309 ASSERT(sdt->event_timer_block.address_space == (u8)ACPI::GenericAddressStructure::AddressSpace::SystemMemory);
310 return PhysicalAddress(sdt->event_timer_block.address);
311}
312u64 HPET::calculate_ticks_in_nanoseconds() const
313{
314 auto* registers_block = (const volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
315 return ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD / registers_block->capabilities.main_counter_tick_period;
316}
317HPET::HPET(PhysicalAddress acpi_hpet)
318 : m_physical_acpi_hpet_table(acpi_hpet)
319 , m_physical_acpi_hpet_registers(find_acpi_hept_registers_block())
320 , m_hpet_mmio_region(MM.allocate_kernel_region(m_physical_acpi_hpet_registers.page_base(), PAGE_SIZE, "HPET MMIO", Region::Access::Read | Region::Access::Write))
321{
322 auto region = MM.allocate_kernel_region(m_physical_acpi_hpet_table.page_base(), (PAGE_SIZE * 2), "HPET Initialization", Region::Access::Read);
323 auto* sdt = (volatile ACPI::Structures::HPET*)region->vaddr().offset(m_physical_acpi_hpet_table.offset_in_page()).as_ptr();
324 m_vendor_id = sdt->pci_vendor_id;
325 m_minimum_tick = sdt->mininum_clock_tick;
326 klog() << "HPET: Minimum clock tick - " << m_minimum_tick;
327
328 auto* registers_block = (volatile HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
329
330 // Note: We must do a 32 bit access to offsets 0x0, or 0x4 only.
331 size_t timers_count = ((registers_block->raw_capabilites.reg >> 8) & 0x1f) + 1;
332 klog() << "HPET: Timers count - " << timers_count;
333 ASSERT(timers_count >= 2);
334 m_comparators.resize(timers_count);
335 auto* capabilities_register = (const volatile HPETCapabilityRegister*)®isters_block->raw_capabilites.reg;
336
337 global_disable();
338
339 m_frequency = NANOSECOND_PERIOD_TO_HERTZ(calculate_ticks_in_nanoseconds());
340 klog() << "HPET: frequency " << m_frequency << " Hz (" << MEGAHERTZ_TO_HERTZ(m_frequency) << " MHz)";
341 ASSERT(capabilities_register->main_counter_tick_period <= ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD);
342
343 // Reset the counter, just in case...
344 registers_block->main_counter_value.reg = 0;
345 if (registers_block->raw_capabilites.reg & (u32)HPETFlags::Attributes::LegacyReplacementRouteCapable)
346 registers_block->configuration.reg |= (u32)HPETFlags::Configuration::LegacyReplacementRoute;
347
348 for (size_t index = 0; index < m_comparators.size(); index++) {
349 bool periodic = is_periodic_capable(index);
350 if (index == 0) {
351 m_comparators[index] = HPETComparator::create(index, 0, periodic);
352 }
353 if (index == 1) {
354 m_comparators[index] = HPETComparator::create(index, 8, periodic);
355 }
356 }
357
358 global_enable();
359}
360}