blob: 354a558f35c112897c94ea74f4813aa1e16fe5c6 [file] [log] [blame]
/* Copyright (c) 2011 The Regents of the University of California
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* Kernel side of ucqs. */
#include <ucq.h>
#include <umem.h>
#include <assert.h>
#include <mm.h>
#include <atomic.h>
/* Proc p needs to be current, and you should have checked that ucq is valid
* memory. We'll assert it here, to catch any of your bugs. =) */
void send_ucq_msg(struct ucq *ucq, struct proc *p, struct event_msg *msg)
{
uintptr_t my_slot = 0;
struct ucq_page *new_page, *old_page;
struct msg_container *my_msg;
assert(is_user_rwaddr(ucq, sizeof(struct ucq)));
/* So we can try to send ucqs to _Ss before they initialize */
if (!ucq->ucq_ready) {
if (__proc_is_mcp(p))
warn("proc %d is _M with an uninitialized ucq %p\n",
p->pid, ucq);
return;
}
/* Bypass fetching/incrementing the counter if we're overflowing, helps
* prevent wraparound issues on the counter (only 12 bits of counter) */
if (ucq->prod_overflow)
goto grab_lock;
/* Grab a potential slot */
my_slot = (uintptr_t)atomic_fetch_and_add(&ucq->prod_idx, 1);
if (slot_is_good(my_slot))
goto have_slot;
/* Warn others to not bother with the fetch_and_add */
ucq->prod_overflow = TRUE;
/* Sanity check */
if (PGOFF(my_slot) > 3000)
warn("Abnormally high counter, something is wrong!");
grab_lock:
/* Lock, for this proc/ucq. Using an irqsave, since we may want to send
* ucq messages from irq context. */
hash_lock_irqsave(p->ucq_hashlock, (long)ucq);
/* Grab a potential slot (again, preventing another DoS) */
my_slot = (uintptr_t)atomic_fetch_and_add(&ucq->prod_idx, 1);
if (slot_is_good(my_slot))
goto unlock_lock;
/* Check to make sure the old_page was good before we do anything too
* intense (we deref it later). Bad pages are likely due to
* user-malfeasance or neglect.
*
* The is_user_rwaddr() check on old_page might catch addresses below
* MMAP_LOWEST_VA, and we can also handle a PF, but we'll explicitly
* check for 0 just to be sure (and it's a likely error). */
old_page = (struct ucq_page*)PTE_ADDR(my_slot);
if (!is_user_rwaddr(old_page, PGSIZE) || !old_page)
goto error_addr_unlock;
/* Things still aren't fixed, so we need to reset everything */
/* Try to get the spare page, so we don't have to mmap a new one */
new_page = (struct ucq_page*)atomic_swap(&ucq->spare_pg, 0);
if (!new_page) {
/* Warn if we have a ridiculous amount of pages in the ucq */
if (atomic_fetch_and_add(&ucq->nr_extra_pgs, 1) >
UCQ_WARN_THRESH)
warn("Over %d pages in ucq %p for pid %d!\n",
UCQ_WARN_THRESH, ucq, p->pid);
/* Giant warning: don't ask for anything other than anonymous
* memory at a non-fixed location. o/w, it may cause a TLB
* shootdown, which grabs the proc_lock, and potentially
* deadlock the system. */
new_page = (struct ucq_page*)do_mmap(p, 0, PGSIZE,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS |
MAP_POPULATE | MAP_PRIVATE,
NULL, 0);
assert(new_page);
assert(!PGOFF(new_page));
} else {
/* If we're using the user-supplied new_page, we need to check
* it */
if (!is_user_rwaddr(new_page, PGSIZE) || PGOFF(new_page))
goto error_addr_unlock;
}
/* Now we have a page. Lets make sure it's set up properly */
new_page->header.cons_next_pg = 0;
new_page->header.nr_cons = 0;
/* Link the old page to the new one, so consumers know how to follow */
old_page->header.cons_next_pg = (uintptr_t)new_page;
/* Set the prod_idx counter to 1 (and the new_page), reserving the first
* slot (number '0') for us (reservation prevents DoS). */
my_slot = (uintptr_t)new_page;
atomic_set(&ucq->prod_idx, my_slot + 1);
/* Fallthrough to clear overflow and unlock */
unlock_lock:
/* Clear the overflow, so new producers will try to get a slot */
ucq->prod_overflow = FALSE;
/* At this point, any normal (non-locking) producers can succeed in
* getting a slot. The ones that failed earlier will fight for the
* lock, then quickly proceed when they get a good slot */
hash_unlock_irqsave(p->ucq_hashlock, (long)ucq);
/* Fall through to having a slot */
have_slot:
/* Sanity check on our slot. */
assert(slot_is_good(my_slot));
/* Convert slot to actual msg_container. Note we never actually deref
* my_slot here (o/w we'd need a rw_addr check). */
my_msg = slot2msg(my_slot);
/* Make sure our msg is user RW */
if (!is_user_rwaddr(my_msg, sizeof(struct msg_container)))
goto error_addr;
/* Finally write the message */
my_msg->ev_msg = *msg;
wmb();
/* Now that the write is done, signal to the consumer that they can
* consume our message (they could have been spinning on it) */
my_msg->ready = TRUE;
return;
error_addr_unlock:
/* Had a bad addr while holding the lock. This is a bit more serious */
warn("Bad addr in ucq page management!");
ucq->prod_overflow = FALSE;
hash_unlock_irqsave(p->ucq_hashlock, (long)ucq);
/* Fall-through to normal error out */
error_addr:
warn("Invalid user address, not sending a message");
/* TODO: consider killing the process here. For now, just catch it.
* For some cases, we have a slot that we never fill in, though if we
* had a bad addr, none of this will work out and the kernel just needs
* to protect itself. */
}
/* Debugging */
#include <smp.h>
#include <pmap.h>
/* Prints the status and up to 25 of the previous messages for the UCQ. */
void print_ucq(struct proc *p, struct ucq *ucq)
{
struct ucq_page *ucq_pg;
uintptr_t old_proc = switch_to(p);
printk("UCQ %p\n", ucq);
printk("prod_idx: %p, cons_idx: %p\n", atomic_read(&ucq->prod_idx),
atomic_read(&ucq->cons_idx));
printk("spare_pg: %p, nr_extra_pgs: %d\n", atomic_read(&ucq->spare_pg),
atomic_read(&ucq->nr_extra_pgs));
printk("prod_overflow: %d\n", ucq->prod_overflow);
/* Try to see our previous ucqs */
for (uintptr_t i = atomic_read(&ucq->prod_idx), count = 0;
slot_is_good(i) && count < 25; i--, count++) {
/* only attempt to print messages on the same page */
if (PTE_ADDR(i) != PTE_ADDR(atomic_read(&ucq->prod_idx)))
break;
printk("Prod idx %p message ready is %p\n", i,
slot2msg(i)->ready);
}
/* look at the chain, starting from cons_idx */
ucq_pg = (struct ucq_page*)PTE_ADDR(atomic_read(&ucq->cons_idx));
for (int i = 0; i < 10 && ucq_pg; i++) {
printk("#%02d: Cons page: %p, nr_cons: %d, next page: %p\n", i,
ucq_pg, ucq_pg->header.nr_cons,
ucq_pg->header.cons_next_pg);
ucq_pg = (struct ucq_page*)(ucq_pg->header.cons_next_pg);
}
switch_back(p, old_proc);
}