// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2023 Google LLC * Author: Vincent Donnefort */ #include #include #include #include #include #include #include #include #define HYP_RB_PAGE_HEAD 1UL #define HYP_RB_PAGE_UPDATE 2UL #define HYP_RB_FLAG_MASK 3UL struct hyp_buffer_page { struct list_head list; struct buffer_data_page *page; unsigned long write; unsigned long entries; u32 id; }; struct hyp_rb_per_cpu { struct trace_buffer_meta *meta; struct hyp_buffer_page *tail_page; struct hyp_buffer_page *reader_page; struct hyp_buffer_page *head_page; struct hyp_buffer_page *bpages; unsigned long nr_pages; unsigned long last_overrun; u64 write_stamp; atomic_t status; }; #define HYP_RB_UNAVAILABLE 0 #define HYP_RB_READY 1 #define HYP_RB_WRITING 2 #define HYP_RB_PANIC 3 DEFINE_PER_CPU(struct hyp_rb_per_cpu, trace_rb); DEFINE_HYP_SPINLOCK(trace_rb_lock); static bool rb_set_flag(struct hyp_buffer_page *bpage, int new_flag) { unsigned long ret, val = (unsigned long)bpage->list.next; ret = cmpxchg((unsigned long *)&bpage->list.next, val, (val & ~HYP_RB_FLAG_MASK) | new_flag); return ret == val; } static struct hyp_buffer_page *rb_hyp_buffer_page(struct list_head *list) { unsigned long ptr = (unsigned long)list & ~HYP_RB_FLAG_MASK; return container_of((struct list_head *)ptr, struct hyp_buffer_page, list); } static struct hyp_buffer_page *rb_next_page(struct hyp_buffer_page *bpage) { return rb_hyp_buffer_page(bpage->list.next); } static bool rb_is_head_page(struct hyp_buffer_page *bpage) { return (unsigned long)bpage->list.prev->next & HYP_RB_PAGE_HEAD; } static struct hyp_buffer_page *rb_set_head_page(struct hyp_rb_per_cpu *cpu_buffer) { struct hyp_buffer_page *bpage, *prev_head; int cnt = 0; again: bpage = prev_head = cpu_buffer->head_page; do { if (rb_is_head_page(bpage)) { cpu_buffer->head_page = bpage; return bpage; } bpage = rb_next_page(bpage); } while (bpage != prev_head); /* We might have race with the writer let's try again */ if (++cnt < 3) goto again; return NULL; } static int rb_swap_reader_page(struct hyp_rb_per_cpu *cpu_buffer) { unsigned long *old_head_link, old_link_val, new_link_val, overrun; struct hyp_buffer_page *head, *reader = cpu_buffer->reader_page; spin: /* Update the cpu_buffer->header_page according to HYP_RB_PAGE_HEAD */ head = rb_set_head_page(cpu_buffer); if (!head) return -ENODEV; /* Connect the reader page around the header page */ reader->list.next = head->list.next; reader->list.prev = head->list.prev; /* The reader page points to the new header page */ rb_set_flag(reader, HYP_RB_PAGE_HEAD); /* * Paired with the cmpxchg in rb_move_tail(). Order the read of the head * page and overrun. */ smp_mb(); overrun = READ_ONCE(cpu_buffer->meta->overrun); /* Try to swap the prev head link to the reader page */ old_head_link = (unsigned long *)&reader->list.prev->next; old_link_val = (*old_head_link & ~HYP_RB_FLAG_MASK) | HYP_RB_PAGE_HEAD; new_link_val = (unsigned long)&reader->list; if (cmpxchg(old_head_link, old_link_val, new_link_val) != old_link_val) goto spin; cpu_buffer->head_page = rb_hyp_buffer_page(reader->list.next); cpu_buffer->head_page->list.prev = &reader->list; cpu_buffer->reader_page = head; cpu_buffer->meta->reader.lost_events = overrun - cpu_buffer->last_overrun; cpu_buffer->meta->reader.id = cpu_buffer->reader_page->id; cpu_buffer->last_overrun = overrun; return 0; } static struct hyp_buffer_page * rb_move_tail(struct hyp_rb_per_cpu *cpu_buffer) { struct hyp_buffer_page *tail_page, *new_tail, *new_head; tail_page = cpu_buffer->tail_page; new_tail = rb_next_page(tail_page); again: /* * We caught the reader ... Let's try to move the head page. * The writer can only rely on ->next links to check if this is head. */ if ((unsigned long)tail_page->list.next & HYP_RB_PAGE_HEAD) { /* The reader moved the head in between */ if (!rb_set_flag(tail_page, HYP_RB_PAGE_UPDATE)) goto again; WRITE_ONCE(cpu_buffer->meta->overrun, cpu_buffer->meta->overrun + new_tail->entries); WRITE_ONCE(meta_pages_lost(cpu_buffer->meta), meta_pages_lost(cpu_buffer->meta) + 1); /* Move the head */ rb_set_flag(new_tail, HYP_RB_PAGE_HEAD); /* The new head is in place, reset the update flag */ rb_set_flag(tail_page, 0); new_head = rb_next_page(new_tail); } local_set(&new_tail->page->commit, 0); new_tail->write = 0; new_tail->entries = 0; WRITE_ONCE(meta_pages_touched(cpu_buffer->meta), meta_pages_touched(cpu_buffer->meta) + 1); cpu_buffer->tail_page = new_tail; return new_tail; } static unsigned long rb_event_size(unsigned long length) { struct ring_buffer_event *event; return length + RB_EVNT_HDR_SIZE + sizeof(event->array[0]); } static struct ring_buffer_event * rb_add_ts_extend(struct ring_buffer_event *event, u64 delta) { event->type_len = RINGBUF_TYPE_TIME_EXTEND; event->time_delta = delta & TS_MASK; event->array[0] = delta >> TS_SHIFT; return (struct ring_buffer_event *)((unsigned long)event + 8); } static struct ring_buffer_event * rb_reserve_next(struct hyp_rb_per_cpu *cpu_buffer, unsigned long length) { unsigned long ts_ext_size = 0, event_size = rb_event_size(length); struct hyp_buffer_page *tail_page = cpu_buffer->tail_page; struct ring_buffer_event *event; unsigned long write, prev_write; u64 ts, time_delta; ts = trace_clock(); time_delta = ts - cpu_buffer->write_stamp; if (test_time_stamp(time_delta)) ts_ext_size = 8; prev_write = tail_page->write; write = prev_write + event_size + ts_ext_size; if (unlikely(write > BUF_PAGE_SIZE)) tail_page = rb_move_tail(cpu_buffer); if (!tail_page->entries) { tail_page->page->time_stamp = ts; time_delta = 0; ts_ext_size = 0; write = event_size; prev_write = 0; } tail_page->write = write; tail_page->entries++; cpu_buffer->write_stamp = ts; event = (struct ring_buffer_event *)(tail_page->page->data + prev_write); if (ts_ext_size) { event = rb_add_ts_extend(event, time_delta); time_delta = 0; } event->type_len = 0; event->time_delta = time_delta; event->array[0] = event_size - RB_EVNT_HDR_SIZE; return event; } void *tracing_reserve_entry(unsigned long length) { struct hyp_rb_per_cpu *cpu_buffer = this_cpu_ptr(&trace_rb); struct ring_buffer_event *rb_event; if (atomic_cmpxchg(&cpu_buffer->status, HYP_RB_READY, HYP_RB_WRITING) != HYP_RB_READY) return NULL; rb_event = rb_reserve_next(cpu_buffer, length); return &rb_event->array[1]; } void tracing_commit_entry(void) { struct hyp_rb_per_cpu *cpu_buffer = this_cpu_ptr(&trace_rb); local_set(&cpu_buffer->tail_page->page->commit, cpu_buffer->tail_page->write); WRITE_ONCE(cpu_buffer->meta->entries, cpu_buffer->meta->entries + 1); /* Paired with rb_cpu_disable_writing() */ atomic_set_release(&cpu_buffer->status, HYP_RB_READY); } static int rb_page_init(struct hyp_buffer_page *bpage, unsigned long hva) { void *hyp_va = (void *)kern_hyp_va(hva); int ret; ret = hyp_pin_shared_mem(hyp_va, hyp_va + PAGE_SIZE); if (ret) return ret; INIT_LIST_HEAD(&bpage->list); bpage->page = (struct buffer_data_page *)hyp_va; local_set(&bpage->page->commit, 0); return 0; } static void rb_page_reset(struct hyp_buffer_page *bpage) { bpage->write = 0; bpage->entries = 0; local_set(&bpage->page->commit, 0); } static bool rb_cpu_loaded(struct hyp_rb_per_cpu *cpu_buffer) { return !!cpu_buffer->bpages; } static bool rb_cpu_panic(struct hyp_rb_per_cpu *cpu_buffer) { return atomic_read(&cpu_buffer->status) == HYP_RB_PANIC; } static int rb_cpu_disable_writing(struct hyp_rb_per_cpu *cpu_buffer) { int prev_status; /* Wait for the buffer to be released */ do { prev_status = atomic_cmpxchg_acquire(&cpu_buffer->status, HYP_RB_READY, HYP_RB_UNAVAILABLE); } while (prev_status == HYP_RB_WRITING); return prev_status; } static int rb_cpu_enable_writing(struct hyp_rb_per_cpu *cpu_buffer) { int prev_status; if (!rb_cpu_loaded(cpu_buffer)) return -ENODEV; prev_status = atomic_cmpxchg(&cpu_buffer->status, HYP_RB_UNAVAILABLE, HYP_RB_READY); return prev_status == HYP_RB_PANIC ? -EBUSY : 0; } static int rb_cpu_reset(struct hyp_rb_per_cpu *cpu_buffer) { struct hyp_buffer_page *bpage; int prev_status; if (!rb_cpu_loaded(cpu_buffer)) return -ENODEV; if (!rb_cpu_panic(cpu_buffer)) return -EBUSY; prev_status = rb_cpu_disable_writing(cpu_buffer); if (!rb_set_head_page(cpu_buffer)) return -ENODEV; cpu_buffer->tail_page = cpu_buffer->head_page; bpage = cpu_buffer->head_page; do { rb_page_reset(bpage); bpage = rb_next_page(bpage); } while (bpage != cpu_buffer->head_page); rb_page_reset(cpu_buffer->reader_page); cpu_buffer->last_overrun = 0; cpu_buffer->write_stamp = 0; cpu_buffer->meta->reader.read = 0; cpu_buffer->meta->reader.lost_events = 0; cpu_buffer->meta->entries = 0; cpu_buffer->meta->overrun = 0; cpu_buffer->meta->read = 0; meta_pages_lost(cpu_buffer->meta) = 0; meta_pages_touched(cpu_buffer->meta) = 0; if (prev_status == HYP_RB_READY) rb_cpu_enable_writing(cpu_buffer); return 0; } static int rb_cpu_teardown(struct hyp_rb_per_cpu *cpu_buffer) { int i; if (!rb_cpu_loaded(cpu_buffer)) return 0; if (rb_cpu_panic(cpu_buffer)) return -EBUSY; rb_cpu_disable_writing(cpu_buffer); hyp_unpin_shared_mem((void *)cpu_buffer->meta, (void *)(cpu_buffer->meta) + PAGE_SIZE); for (i = 0; i < cpu_buffer->nr_pages; i++) { struct hyp_buffer_page *bpage = &cpu_buffer->bpages[i]; if (!bpage->page) continue; hyp_unpin_shared_mem((void *)bpage->page, (void *)bpage->page + PAGE_SIZE); } hyp_free(cpu_buffer->bpages); cpu_buffer->bpages = 0; return 0; } static bool rb_cpu_fits_desc(struct rb_page_desc *pdesc, unsigned long desc_end) { unsigned long *end; /* Check we can at least read nr_pages */ if ((unsigned long)&pdesc->nr_page_va >= desc_end) return false; end = &pdesc->page_va[pdesc->nr_page_va]; return (unsigned long)end <= desc_end; } static int rb_cpu_init(struct rb_page_desc *pdesc, struct hyp_rb_per_cpu *cpu_buffer) { struct hyp_buffer_page *bpage; int i, ret; /* At least 1 reader page and one head */ if (pdesc->nr_page_va < 2) return -EINVAL; if (rb_cpu_loaded(cpu_buffer) || rb_cpu_panic(cpu_buffer)) return -EBUSY; bpage = hyp_alloc(sizeof(*bpage) * pdesc->nr_page_va); if (!bpage) return hyp_alloc_errno(); cpu_buffer->bpages = bpage; cpu_buffer->meta = (struct trace_buffer_meta *)kern_hyp_va(pdesc->meta_va); ret = hyp_pin_shared_mem((void *)cpu_buffer->meta, ((void *)cpu_buffer->meta) + PAGE_SIZE); if (ret) { hyp_free(cpu_buffer->bpages); return ret; } memset(cpu_buffer->meta, 0, sizeof(*cpu_buffer->meta)); cpu_buffer->meta->meta_page_size = PAGE_SIZE; cpu_buffer->meta->nr_subbufs = cpu_buffer->nr_pages; /* The reader page is not part of the ring initially */ ret = rb_page_init(bpage, pdesc->page_va[0]); if (ret) goto err; cpu_buffer->nr_pages = 1; cpu_buffer->reader_page = bpage; cpu_buffer->tail_page = bpage + 1; cpu_buffer->head_page = bpage + 1; for (i = 1; i < pdesc->nr_page_va; i++) { ret = rb_page_init(++bpage, pdesc->page_va[i]); if (ret) goto err; bpage->list.next = &(bpage + 1)->list; bpage->list.prev = &(bpage - 1)->list; bpage->id = i; cpu_buffer->nr_pages = i + 1; } /* Close the ring */ bpage->list.next = &cpu_buffer->tail_page->list; cpu_buffer->tail_page->list.prev = &bpage->list; /* The last init'ed page points to the head page */ rb_set_flag(bpage, HYP_RB_PAGE_HEAD); cpu_buffer->last_overrun = 0; return 0; err: WARN_ON(rb_cpu_teardown(cpu_buffer)); return ret; } void __pkvm_update_clock_tracing(u32 mult, u32 shift, u64 epoch_ns, u64 epoch_cyc) { int cpu; /* After this loop, all CPUs are observing the new bank... */ for (cpu = 0; cpu < hyp_nr_cpus; cpu++) { struct hyp_rb_per_cpu *cpu_buffer = per_cpu_ptr(&trace_rb, cpu); while (atomic_read(&cpu_buffer->status) == HYP_RB_WRITING); } /* ...we can now override the old one and swap. */ trace_clock_update(mult, shift, epoch_ns, epoch_cyc); } int __pkvm_swap_reader_tracing(unsigned int cpu) { struct hyp_rb_per_cpu *cpu_buffer; int ret = 0; if (cpu >= hyp_nr_cpus) return -EINVAL; hyp_spin_lock(&trace_rb_lock); cpu_buffer = per_cpu_ptr(&trace_rb, cpu); if (!rb_cpu_loaded(cpu_buffer)) ret = -ENODEV; else if (rb_cpu_panic(cpu_buffer)) ret = -EBUSY; else ret = rb_swap_reader_page(cpu_buffer); hyp_spin_unlock(&trace_rb_lock); return ret; } static int __pkvm_teardown_tracing_locked(void) { int cpu, ret; hyp_assert_lock_held(&trace_rb_lock); for (cpu = 0; cpu < hyp_nr_cpus; cpu++) { struct hyp_rb_per_cpu *cpu_buffer = per_cpu_ptr(&trace_rb, cpu); ret = rb_cpu_teardown(cpu_buffer); if (ret) return ret; } return 0; } int __pkvm_teardown_tracing(void) { int ret; hyp_spin_lock(&trace_rb_lock); ret = __pkvm_teardown_tracing_locked(); hyp_spin_unlock(&trace_rb_lock); return ret; } int __pkvm_load_tracing(unsigned long desc_hva, size_t desc_size) { struct hyp_trace_desc *desc = (struct hyp_trace_desc *)kern_hyp_va(desc_hva); struct trace_page_desc *trace_pdesc = &desc->page_desc; struct rb_page_desc *pdesc; int ret, pdesc_cpu; if (!desc_size || !PAGE_ALIGNED(desc_hva) || !PAGE_ALIGNED(desc_size)) return -EINVAL; ret = __pkvm_host_donate_hyp(hyp_virt_to_pfn((void *)desc), desc_size >> PAGE_SHIFT); if (ret) return ret; hyp_spin_lock(&trace_rb_lock); for_each_rb_page_desc(pdesc, pdesc_cpu, trace_pdesc) { struct hyp_rb_per_cpu *cpu_buffer; unsigned int cpu; ret = -EINVAL; if (!rb_cpu_fits_desc(pdesc, desc_hva + desc_size)) break; cpu = pdesc->cpu; if (cpu >= hyp_nr_cpus) break; cpu_buffer = per_cpu_ptr(&trace_rb, cpu); ret = rb_cpu_init(pdesc, cpu_buffer); if (ret) break; } if (ret && ret != -EBUSY) WARN_ON(__pkvm_teardown_tracing_locked()); hyp_spin_unlock(&trace_rb_lock); WARN_ON(__pkvm_hyp_donate_host(hyp_virt_to_pfn((void *)desc), desc_size >> PAGE_SHIFT)); return ret; } int __pkvm_enable_tracing(bool enable) { int cpu, ret = enable ? -EINVAL : 0; hyp_spin_lock(&trace_rb_lock); for (cpu = 0; cpu < hyp_nr_cpus; cpu++) { struct hyp_rb_per_cpu *cpu_buffer = per_cpu_ptr(&trace_rb, cpu); if (enable) { ret = rb_cpu_enable_writing(cpu_buffer); if (ret) break; } else { ret = rb_cpu_disable_writing(cpu_buffer); if (ret == HYP_RB_PANIC) ret = -EBUSY; else ret = 0; if (ret) break; } } hyp_spin_unlock(&trace_rb_lock); return ret; } int __pkvm_reset_tracing(unsigned int cpu) { int ret = 0; if (cpu >= hyp_nr_cpus) return -EINVAL; hyp_spin_lock(&trace_rb_lock); ret = rb_cpu_reset(per_cpu_ptr(&trace_rb, cpu)); hyp_spin_unlock(&trace_rb_lock); return ret; } void __pkvm_panic_tracing(void) { #ifdef CONFIG_PKVM_DUMP_TRACE_ON_PANIC int cpu; hyp_spin_lock(&trace_rb_lock); for (cpu = 0; cpu < hyp_nr_cpus; cpu++) { struct hyp_rb_per_cpu *cpu_buffer = per_cpu_ptr(&trace_rb, cpu); int prev_status, skipped = 0; if (!rb_cpu_loaded(cpu_buffer)) continue; do { prev_status = atomic_cmpxchg_acquire(&cpu_buffer->status, HYP_RB_READY, HYP_RB_PANIC); } while (prev_status == HYP_RB_WRITING); /* Allow the host to read the very last events */ while (cpu_buffer->tail_page != cpu_buffer->reader_page) { struct hyp_buffer_page *prev_reader = cpu_buffer->reader_page; if (rb_swap_reader_page(cpu_buffer)) break; /* * The reader is still on the previous reader page and events there can * still be read. */ if (++skipped == 1) continue; WRITE_ONCE(cpu_buffer->meta->overrun, cpu_buffer->meta->overrun + prev_reader->entries); cpu_buffer->meta->reader.lost_events = cpu_buffer->meta->overrun - cpu_buffer->last_overrun; WRITE_ONCE(meta_pages_lost(cpu_buffer->meta), meta_pages_lost(cpu_buffer->meta) + 1); } } hyp_spin_unlock(&trace_rb_lock); #endif }