UCQs (XCC)
[akaros.git] / kern / src / ucq.c
1 /* Copyright (c) 2011 The Regents of the University of California
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details.
4  *
5  * Kernel side of ucqs. */
6
7 #include <ucq.h>
8 #include <umem.h>
9 #include <assert.h>
10 #include <mm.h>
11 #include <atomic.h>
12
13 /* Proc p needs to be current, and you should have checked that ucq is valid
14  * memory.  We'll assert it here, to catch any of your bugs.  =) */
15 void send_ucq_msg(struct ucq *ucq, struct proc *p, struct event_msg *msg)
16 {
17         uintptr_t my_slot = 0;
18         struct ucq_page *new_page, *old_page;
19         struct msg_container *my_msg;
20
21         assert(is_user_rwaddr(ucq, sizeof(struct ucq)));
22         /* So we can try to send ucqs to _Ss before they initialize */
23         if (!ucq->ucq_ready)
24                 return;
25         /* Bypass fetching/incrementing the counter if we're overflowing, helps
26          * prevent wraparound issues on the counter (only 12 bits of counter) */
27         if (ucq->prod_overflow)
28                 goto grab_lock;
29         /* Grab a potential slot */
30         my_slot = (uintptr_t)atomic_fetch_and_add(&ucq->prod_idx, 1);
31         if (slot_is_good(my_slot))
32                 goto have_slot;
33         /* Warn others to not bother with the fetch_and_add */
34         ucq->prod_overflow = TRUE;
35         /* Sanity check */
36         if (PGOFF(my_slot) > 3000)
37                 warn("Abnormally high counter, there's probably something wrong!");
38 grab_lock:
39         /* TODO: use a hashlock, instead of the proc lock.  think about irqsave.  do
40          * we send events from IRQ context?  The proc_lock isn't irqsave */
41         spin_lock(&p->proc_lock);
42         /* Grab a potential slot (again, preventing another DoS) */
43         my_slot = (uintptr_t)atomic_fetch_and_add(&ucq->prod_idx, 1);
44         if (slot_is_good(my_slot))
45                 goto unlock_lock;
46         /* Check to make sure the old_page was good before we do anything too
47          * intense (we deref it later).  Bad pages are likely due to
48          * user-malfeasance or neglect.
49          *
50          * The is_user_rwaddr() check on old_page might catch addresses below
51          * MMAP_LOWEST_VA, and we can also handle a PF, but we'll explicitly check
52          * for 0 just to be sure (and it's a likely error). */
53         old_page = (struct ucq_page*)PTE_ADDR(my_slot);
54         if (!is_user_rwaddr(old_page, PGSIZE) || !old_page)
55                 goto error_addr_unlock;
56         /* Things still aren't fixed, so we need to reset everything */
57         /* Try to get the spare page, so we don't have to mmap a new one */
58         new_page = (struct ucq_page*)atomic_swap(&ucq->spare_pg, 0);
59         if (!new_page) {
60                 /* Warn if we have a ridiculous amount of pages in the ucq */
61                 if (atomic_fetch_and_add(&ucq->nr_extra_pgs, 1) > UCQ_WARN_THRESH)
62                         warn("Over %d pages in ucq %08p!\n", UCQ_WARN_THRESH, ucq);
63                 /* TODO: use a proc_lock/mm_lock or call the external one.  Need to do
64                  * this for now since we don't have hash locks yet HASH LOCK */
65                 new_page = (struct ucq_page*)__do_mmap(p, 0, PGSIZE,
66                                                        PROT_READ | PROT_WRITE,
67                                                        MAP_ANON | MAP_POPULATE, 0, 0);
68                 assert(new_page);
69                 assert(!PGOFF(new_page));
70         } else {
71                 /* If we're using the user-supplied new_page, we need to check it */
72                 if (!is_user_rwaddr(new_page, PGSIZE) || PGOFF(new_page))
73                         goto error_addr_unlock;
74         }
75         /* Now we have a page.  Lets make sure it's set up properly */
76         new_page->header.cons_next_pg = 0;
77         new_page->header.nr_cons = 0;
78         /* Link the old page to the new one, so consumers know how to follow */
79         old_page->header.cons_next_pg = (uintptr_t)new_page;
80         /* Set the prod_idx counter to 1 (and the new_page), reserving the first
81          * slot (number '0') for us (reservation prevents DoS). */
82         my_slot = (uintptr_t)new_page;
83         atomic_set(&ucq->prod_idx, my_slot + 1);
84         /* Fallthrough to clear overflow and unlock */
85 unlock_lock:
86         /* Clear the overflow, so new producers will try to get a slot */
87         ucq->prod_overflow = FALSE;
88         /* At this point, any normal (non-locking) producers can succeed in getting
89          * a slot.  The ones that failed earlier will fight for the lock, then
90          * quickly proceed when they get a good slot */
91         spin_unlock(&p->proc_lock);     /* TODO HASH LOCK */
92         /* Fall through to having a slot */
93 have_slot:
94         /* Sanity check on our slot. */
95         assert(slot_is_good(my_slot));
96         /* Convert slot to actual msg_container.  Note we never actually deref
97          * my_slot here (o/w we'd need a rw_addr check). */
98         my_msg = slot2msg(my_slot);
99         /* Make sure our msg is user RW */
100         if (!is_user_rwaddr(my_msg, sizeof(struct msg_container)))
101                 goto error_addr;
102         /* Finally write the message */
103         my_msg->ev_msg = *msg;
104         wmb();
105         /* Now that the write is done, signal to the consumer that they can consume
106          * our message (they could have been spinning on it) */
107         my_msg->ready = TRUE;
108         return;
109 error_addr_unlock:
110         /* Had a bad addr while holding the lock.  This is a bit more serious */
111         warn("Bad addr in ucq page management!");
112         ucq->prod_overflow = FALSE;
113         spin_unlock(&p->proc_lock);     /* TODO HASH LOCK */
114         /* Fall-through to normal error out */
115 error_addr:
116         warn("Invalid user address, not sending a message");
117         /* TODO: consider killing the process here.  For now, just catch it.  For
118          * some cases, we have a slot that we never fill in, though if we had a bad
119          * addr, none of this will work out and the kernel just needs to protect
120          * itself. */
121         return;
122 }