diff options
author | Mike Gerdts <mike.gerdts@joyent.com> | 2018-03-16 20:42:29 +0000 |
---|---|---|
committer | Mike Gerdts <mike.gerdts@joyent.com> | 2018-03-23 16:25:44 +0000 |
commit | 7892cb3dceef9f2cbcd853e66fb05601a94bbba2 (patch) | |
tree | 21b5151c94e3edfd63934d6ca52659ae42702057 | |
parent | 929399046cc464d1c38a4d4facf93476166a5662 (diff) | |
download | illumos-joyent-7892cb3dceef9f2cbcd853e66fb05601a94bbba2.tar.gz |
OS-6720 uart-ignorant guest sends bhyve into a spin
OS-6715 extraneous rxfifo_available() in uart_sock_drain()
Reviewed by: Patrick Mooney <patrick.mooney@joyent.com>
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
Reviewed by: John Levon <john.levon@joyent.com>
Approved by: John Levon <john.levon@joyent.com>
21 files changed, 2326 insertions, 106 deletions
diff --git a/usr/contrib/freebsd/sys/queue.h b/usr/contrib/freebsd/sys/queue.h new file mode 100644 index 0000000000..f26c492af1 --- /dev/null +++ b/usr/contrib/freebsd/sys/queue.h @@ -0,0 +1,787 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD$ + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#include <sys/cdefs.h> + +/* + * This file defines four types of data structures: singly-linked lists, + * singly-linked tail queues, lists and tail queues. + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A singly-linked tail queue is headed by a pair of pointers, one to the + * head of the list and the other to the tail of the list. The elements are + * singly linked for minimum space and pointer manipulation overhead at the + * expense of O(n) removal for arbitrary elements. New elements can be added + * to the list after an existing element, at the head of the list, or at the + * end of the list. Elements being removed from the head of the tail queue + * should use the explicit macro for this purpose for optimum efficiency. + * A singly-linked tail queue may only be traversed in the forward direction. + * Singly-linked tail queues are ideal for applications with large datasets + * and few or no removals or for implementing a FIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may be traversed in either direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * For details on the use of these macros, see the queue(3) manual page. + * + * Below is a summary of implemented functions where: + * + means the macro is available + * - means the macro is not available + * s means the macro is available but is slow (runs in O(n) time) + * + * SLIST LIST STAILQ TAILQ + * _HEAD + + + + + * _CLASS_HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _CLASS_ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - + - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_FROM + + + + + * _FOREACH_SAFE + + + + + * _FOREACH_FROM_SAFE + + + + + * _FOREACH_REVERSE - - - + + * _FOREACH_REVERSE_FROM - - - + + * _FOREACH_REVERSE_SAFE - - - + + * _FOREACH_REVERSE_FROM_SAFE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _CONCAT s s + + + * _REMOVE_AFTER + - + - + * _REMOVE_HEAD + - + - + * _REMOVE s + s + + * _SWAP + + + + + * + */ +#ifdef QUEUE_MACRO_DEBUG +/* Store the last 2 places the queue element or head was altered */ +struct qm_trace { + unsigned long lastline; + unsigned long prevline; + const char *lastfile; + const char *prevfile; +}; + +#define TRACEBUF struct qm_trace trace; +#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } , +#define TRASHIT(x) do {(x) = (void *)-1;} while (0) +#define QMD_SAVELINK(name, link) void **name = (void *)&(link) + +#define QMD_TRACE_HEAD(head) do { \ + (head)->trace.prevline = (head)->trace.lastline; \ + (head)->trace.prevfile = (head)->trace.lastfile; \ + (head)->trace.lastline = __LINE__; \ + (head)->trace.lastfile = __FILE__; \ +} while (0) + +#define QMD_TRACE_ELEM(elem) do { \ + (elem)->trace.prevline = (elem)->trace.lastline; \ + (elem)->trace.prevfile = (elem)->trace.lastfile; \ + (elem)->trace.lastline = __LINE__; \ + (elem)->trace.lastfile = __FILE__; \ +} while (0) + +#else +#define QMD_TRACE_ELEM(elem) +#define QMD_TRACE_HEAD(head) +#define QMD_SAVELINK(name, link) +#define TRACEBUF +#define TRACEBUF_INITIALIZER +#define TRASHIT(x) +#endif /* QUEUE_MACRO_DEBUG */ + +#ifdef __cplusplus +/* + * In C++ there can be structure lists and class lists: + */ +#define QUEUE_TYPEOF(type) type +#else +#define QUEUE_TYPEOF(type) struct type +#endif + +/* + * Singly-linked List declarations. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_CLASS_HEAD(name, type) \ +struct name { \ + class type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +#define SLIST_CLASS_ENTRY(type) \ +struct { \ + class type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_CONCAT(head1, head2, type, field) do { \ + QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \ + if (curelm == NULL) { \ + if ((SLIST_FIRST(head1) = SLIST_FIRST(head2)) != NULL) \ + SLIST_INIT(head2); \ + } else if (SLIST_FIRST(head2) != NULL) { \ + while (SLIST_NEXT(curelm, field) != NULL) \ + curelm = SLIST_NEXT(curelm, field); \ + SLIST_NEXT(curelm, field) = SLIST_FIRST(head2); \ + SLIST_INIT(head2); \ + } \ +} while (0) + +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) + +#define SLIST_FIRST(head) ((head)->slh_first) + +#define SLIST_FOREACH(var, head, field) \ + for ((var) = SLIST_FIRST((head)); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != NULL; \ + (varp) = &SLIST_NEXT((var), field)) + +#define SLIST_INIT(head) do { \ + SLIST_FIRST((head)) = NULL; \ +} while (0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ + SLIST_NEXT((slistelm), field) = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ + SLIST_FIRST((head)) = (elm); \ +} while (0) + +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ + if (SLIST_FIRST((head)) == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \ + while (SLIST_NEXT(curelm, field) != (elm)) \ + curelm = SLIST_NEXT(curelm, field); \ + SLIST_REMOVE_AFTER(curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + SLIST_NEXT(elm, field) = \ + SLIST_NEXT(SLIST_NEXT(elm, field), field); \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ +} while (0) + +#define SLIST_SWAP(head1, head2, type) do { \ + QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \ + SLIST_FIRST(head1) = SLIST_FIRST(head2); \ + SLIST_FIRST(head2) = swap_first; \ +} while (0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#define STAILQ_CLASS_HEAD(name, type) \ +struct name { \ + class type *stqh_first; /* first element */ \ + class type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +#define STAILQ_CLASS_ENTRY(type) \ +struct { \ + class type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (0) + +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) + +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#define STAILQ_FOREACH(var, head, field) \ + for((var) = STAILQ_FIRST((head)); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_INIT(head) do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_NEXT((tqelm), field) = (elm); \ +} while (0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_FIRST((head)) = (elm); \ +} while (0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? NULL : \ + __containerof((head)->stqh_last, \ + QUEUE_TYPEOF(type), field.stqe_next)) + +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } \ + else { \ + QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ + if ((STAILQ_NEXT(elm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_SWAP(head1, head2, type) do { \ + QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \ + QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \ + STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_FIRST(head2) = swap_first; \ + (head2)->stqh_last = swap_last; \ + if (STAILQ_EMPTY(head1)) \ + (head1)->stqh_last = &STAILQ_FIRST(head1); \ + if (STAILQ_EMPTY(head2)) \ + (head2)->stqh_last = &STAILQ_FIRST(head2); \ +} while (0) + + +/* + * List declarations. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_CLASS_HEAD(name, type) \ +struct name { \ + class type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +#define LIST_CLASS_ENTRY(type) \ +struct { \ + class type *le_next; /* next element */ \ + class type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ + +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_LIST_CHECK_HEAD(head, field) do { \ + if (LIST_FIRST((head)) != NULL && \ + LIST_FIRST((head))->field.le_prev != \ + &LIST_FIRST((head))) \ + panic("Bad list head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_LIST_CHECK_NEXT(elm, field) do { \ + if (LIST_NEXT((elm), field) != NULL && \ + LIST_NEXT((elm), field)->field.le_prev != \ + &((elm)->field.le_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_LIST_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.le_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_LIST_CHECK_HEAD(head, field) +#define QMD_LIST_CHECK_NEXT(elm, field) +#define QMD_LIST_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define LIST_CONCAT(head1, head2, type, field) do { \ + QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \ + if (curelm == NULL) { \ + if ((LIST_FIRST(head1) = LIST_FIRST(head2)) != NULL) { \ + LIST_FIRST(head2)->field.le_prev = \ + &LIST_FIRST((head1)); \ + LIST_INIT(head2); \ + } \ + } else if (LIST_FIRST(head2) != NULL) { \ + while (LIST_NEXT(curelm, field) != NULL) \ + curelm = LIST_NEXT(curelm, field); \ + LIST_NEXT(curelm, field) = LIST_FIRST(head2); \ + LIST_FIRST(head2)->field.le_prev = &LIST_NEXT(curelm, field); \ + LIST_INIT(head2); \ + } \ +} while (0) + +#define LIST_EMPTY(head) ((head)->lh_first == NULL) + +#define LIST_FIRST(head) ((head)->lh_first) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_INIT(head) do { \ + LIST_FIRST((head)) = NULL; \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + QMD_LIST_CHECK_NEXT(listelm, field); \ + if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ + LIST_NEXT((listelm), field)->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_NEXT((listelm), field) = (elm); \ + (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_LIST_CHECK_PREV(listelm, field); \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + LIST_NEXT((elm), field) = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + QMD_LIST_CHECK_HEAD((head), field); \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ +} while (0) + +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_PREV(elm, head, type, field) \ + ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ + __containerof((elm)->field.le_prev, \ + QUEUE_TYPEOF(type), field.le_next)) + +#define LIST_REMOVE(elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.le_next); \ + QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ + QMD_LIST_CHECK_NEXT(elm, field); \ + QMD_LIST_CHECK_PREV(elm, field); \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ +} while (0) + +#define LIST_SWAP(head1, head2, type, field) do { \ + QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \ + LIST_FIRST((head1)) = LIST_FIRST((head2)); \ + LIST_FIRST((head2)) = swap_tmp; \ + if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ + if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ +} while (0) + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ +} + +#define TAILQ_CLASS_HEAD(name, type) \ +struct name { \ + class type *tqh_first; /* first element */ \ + class type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ +} + +#define TAILQ_CLASS_ENTRY(type) \ +struct { \ + class type *tqe_next; /* next element */ \ + class type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ +} + +/* + * Tail queue functions. + */ +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_TAILQ_CHECK_HEAD(head, field) do { \ + if (!TAILQ_EMPTY(head) && \ + TAILQ_FIRST((head))->field.tqe_prev != \ + &TAILQ_FIRST((head))) \ + panic("Bad tailq head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_TAIL(head, field) do { \ + if (*(head)->tqh_last != NULL) \ + panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ + if (TAILQ_NEXT((elm), field) != NULL && \ + TAILQ_NEXT((elm), field)->field.tqe_prev != \ + &((elm)->field.tqe_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_TAILQ_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.tqe_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_TAILQ_CHECK_HEAD(head, field) +#define QMD_TAILQ_CHECK_TAIL(head, headname) +#define QMD_TAILQ_CHECK_NEXT(elm, field) +#define QMD_TAILQ_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + QMD_TRACE_HEAD(head1); \ + QMD_TRACE_HEAD(head2); \ + } \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST((head)); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QMD_TAILQ_CHECK_NEXT(listelm, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + } \ + TAILQ_NEXT((listelm), field) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_TAILQ_CHECK_PREV(listelm, field); \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + TAILQ_NEXT((elm), field) = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + QMD_TAILQ_CHECK_HEAD(head, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + QMD_TAILQ_CHECK_TAIL(head, field); \ + TAILQ_NEXT((elm), field) = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ + QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ + QMD_TAILQ_CHECK_NEXT(elm, field); \ + QMD_TAILQ_CHECK_PREV(elm, field); \ + if ((TAILQ_NEXT((elm), field)) != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else { \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + QMD_TRACE_HEAD(head); \ + } \ + *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_SWAP(head1, head2, type, field) do { \ + QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \ + QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/usr/src/cmd/bhyve/Makefile b/usr/src/cmd/bhyve/Makefile index 7975589c66..ee5a139e0c 100644 --- a/usr/src/cmd/bhyve/Makefile +++ b/usr/src/cmd/bhyve/Makefile @@ -21,6 +21,14 @@ include ../Makefile.cmd include ../Makefile.cmd.64 include ../Makefile.ctf +SUBDIRS = test + +all := TARGET = all +install := TARGET = install +clean := TARGET = clean +clobber := TARGET = clobber +lint := TARGET = lint + SRCS = acpi.c \ atkbdc.c \ bhyvegc.c \ @@ -34,6 +42,7 @@ SRCS = acpi.c \ inout.c \ ioapic.c \ mem.c \ + mevent.c \ mptbl.c \ pci_ahci.c \ pci_e82545.c \ @@ -74,6 +83,11 @@ OBJS = $(SRCS:.c=.o) CLEANFILES = $(PROG) CLOBBERFILES = $(ROOTUSRSBINPROG) $(ZHYVE) +MEVENT_TEST_PROG = mevent_test +MEVENT_TEST_SRCS = mevent.c mevent_test.c +MEVENT_TEST_OBJS = $(MEVENT_TEST_SRCS:.c=.o) +CLEANFILES += $(MEVENT_TEST_PROG) $(MEVENT_TEST_OBJS) + CFLAGS += $(CCVERBOSE) -_gcc=-Wimplicit-function-declaration -_gcc=-Wno-parentheses CPPFLAGS = -I$(COMPAT)/freebsd -I$(CONTRIB)/freebsd \ -I$(COMPAT)/freebsd/amd64 -I$(CONTRIB)/freebsd/amd64 \ @@ -96,20 +110,31 @@ POST_PROCESS += ; $(GENSETDEFS) $@ # Real main is in zhyve.c bhyverun.o := CPPFLAGS += -Dmain=bhyve_main -all: $(PROG) +all: $(PROG) $(MEVENT_TEST_PROG) $(SUBDIRS) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDFLAGS) $(LDLIBS) $(POST_PROCESS) -install: all $(ROOTUSRSBINPROG) +$(MEVENT_TEST_PROG): $(MEVENT_TEST_OBJS) + $(LINK.c) -o $@ $(MEVENT_TEST_OBJS) -lsocket + +install: all $(ROOTUSRSBINPROG) $(SUBDIRS) $(RM) $(ZHYVE) $(LN) $(ROOTUSRSBIN)/$(PROG) $(ZHYVE) -clean: +clean: $(SUBDIRS) $(RM) $(OBJS) $(CLEANFILES) -lint: lint_SRCS +clobber: clean $(SUBDIRS) + $(RM) $(CLOBBERFILES) + +lint: lint_SRCS $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: include ../Makefile.targ diff --git a/usr/src/cmd/bhyve/bhyverun.c b/usr/src/cmd/bhyve/bhyverun.c index dbc2414e23..56240356ae 100644 --- a/usr/src/cmd/bhyve/bhyverun.c +++ b/usr/src/cmd/bhyve/bhyverun.c @@ -36,7 +36,7 @@ * http://www.illumos.org/license/CDDL. * * Copyright 2015 Pluribus Networks Inc. - * Copyright 2017 Joyent, Inc. + * Copyright 2018 Joyent, Inc. */ #include <sys/cdefs.h> @@ -85,9 +85,7 @@ __FBSDID("$FreeBSD$"); #include "fwctl.h" #include "ioapic.h" #include "mem.h" -#ifdef __FreeBSD__ #include "mevent.h" -#endif #include "mptbl.h" #include "pci_emul.h" #include "pci_irq.h" @@ -1124,15 +1122,14 @@ main(int argc, char *argv[]) /* * Head off to the main event dispatch loop */ -#ifdef __FreeBSD__ mevent_dispatch(); -#else +#ifndef __FreeBSD if (vm_started_cb != NULL) { vm_started_cb(); } +#endif pthread_exit(NULL); -#endif exit(1); } diff --git a/usr/src/cmd/bhyve/mevent.c b/usr/src/cmd/bhyve/mevent.c index adc047db71..bd36c03393 100644 --- a/usr/src/cmd/bhyve/mevent.c +++ b/usr/src/cmd/bhyve/mevent.c @@ -27,6 +27,10 @@ */ /* + * Copyright 2018 Joyent, Inc. + */ + +/* * Micro event library for FreeBSD, designed for a single i/o thread * using kqueue, and having events be persistent by default. */ @@ -47,7 +51,14 @@ __FBSDID("$FreeBSD$"); #ifndef WITHOUT_CAPSICUM #include <sys/capsicum.h> #endif +#ifdef __FreeBSD__ #include <sys/event.h> +#else +#include <port.h> +#include <sys/poll.h> +#include <sys/siginfo.h> +#include <sys/queue.h> +#endif #include <sys/time.h> #include <pthread.h> @@ -73,12 +84,21 @@ struct mevent { void (*me_func)(int, enum ev_type, void *); #define me_msecs me_fd int me_fd; +#ifdef __FreeBSD__ int me_timid; +#else + timer_t me_timid; +#endif enum ev_type me_type; void *me_param; int me_cq; int me_state; int me_closefd; +#ifndef __FreeBSD__ + port_notify_t me_notify; + struct sigevent me_sigev; + boolean_t me_auto_requeue; +#endif LIST_ENTRY(mevent) me_list; }; @@ -124,7 +144,7 @@ mevent_notify(void) write(mevent_pipefd[1], &c, 1); } } - +#ifdef __FreeBSD__ static int mevent_kq_filter(struct mevent *mevp) { @@ -244,6 +264,155 @@ mevent_handle(struct kevent *kev, int numev) } } +#else /* __FreeBSD__ */ + +static void +mevent_update_one(struct mevent *mevp) +{ + int portfd = mevp->me_notify.portnfy_port; + + switch (mevp->me_type) { + case EVF_READ: + case EVF_WRITE: + mevp->me_auto_requeue = B_FALSE; + + switch (mevp->me_state) { + case MEV_ADD: + case MEV_ENABLE: + { + int events; + + events = (mevp->me_type == EVF_READ) ? POLLIN : POLLOUT; + + if (port_associate(portfd, PORT_SOURCE_FD, mevp->me_fd, + events, mevp) != 0) { + (void) fprintf(stderr, + "port_associate fd %d %p failed: %s\n", + mevp->me_fd, mevp, strerror(errno)); + } + return; + } + case MEV_DISABLE: + case MEV_DEL_PENDING: + /* + * A disable that comes in while an event is being + * handled will result in an ENOENT. + */ + if (port_dissociate(portfd, PORT_SOURCE_FD, + mevp->me_fd) != 0 && errno != ENOENT) { + (void) fprintf(stderr, "port_dissociate " + "portfd %d fd %d mevp %p failed: %s\n", + portfd, mevp->me_fd, mevp, strerror(errno)); + } + return; + default: + goto abort; + } + + case EVF_TIMER: + mevp->me_auto_requeue = B_TRUE; + + switch (mevp->me_state) { + case MEV_ADD: + case MEV_ENABLE: + { + struct itimerspec it = { 0 }; + + mevp->me_sigev.sigev_notify = SIGEV_PORT; + mevp->me_sigev.sigev_value.sival_ptr = &mevp->me_notify; + + if (timer_create(CLOCK_REALTIME, &mevp->me_sigev, + &mevp->me_timid) != 0) { + (void) fprintf(stderr, + "timer_create failed: %s", strerror(errno)); + return; + } + + /* The first timeout */ + it.it_value.tv_sec = mevp->me_msecs / MILLISEC; + it.it_value.tv_nsec = + MSEC2NSEC(mevp->me_msecs % MILLISEC); + /* Repeat at the same interval */ + it.it_interval = it.it_value; + + if (timer_settime(mevp->me_timid, 0, &it, NULL) != 0) { + (void) fprintf(stderr, "timer_settime failed: " + "%s", strerror(errno)); + } + return; + } + case MEV_DISABLE: + case MEV_DEL_PENDING: + if (timer_delete(mevp->me_timid) != 0) { + (void) fprintf(stderr, "timer_delete failed: " + "%s", strerror(errno)); + } + return; + default: + goto abort; + } + default: + /* EVF_SIGNAL not yet implemented. */ + goto abort; + } + +abort: + (void) fprintf(stderr, "%s: unhandled type %d state %d\n", __func__, + mevp->me_type, mevp->me_state); + abort(); +} + +static void +mevent_update_pending(int portfd) +{ + struct mevent *mevp, *tmpp; + + mevent_qlock(); + + LIST_FOREACH_SAFE(mevp, &change_head, me_list, tmpp) { + mevp->me_notify.portnfy_port = portfd; + mevp->me_notify.portnfy_user = mevp; + if (mevp->me_closefd) { + /* + * A close of the file descriptor will remove the + * event + */ + (void) close(mevp->me_fd); + mevp->me_fd = -1; + } else { + mevent_update_one(mevp); + } + + mevp->me_cq = 0; + LIST_REMOVE(mevp, me_list); + + if (mevp->me_state == MEV_DEL_PENDING) { + free(mevp); + } else { + LIST_INSERT_HEAD(&global_head, mevp, me_list); + } + } + + mevent_qunlock(); +} + +static void +mevent_handle_pe(port_event_t *pe) +{ + struct mevent *mevp = pe->portev_user; + + mevent_qunlock(); + + (*mevp->me_func)(mevp->me_fd, mevp->me_type, mevp->me_param); + + mevent_qlock(); + if (!mevp->me_cq && !mevp->me_auto_requeue) { + mevent_update_one(mevp); + } + mevent_qunlock(); +} +#endif + struct mevent * mevent_add(int tfd, enum ev_type type, void (*func)(int, enum ev_type, void *), void *param) @@ -400,11 +569,16 @@ mevent_set_name(void) void mevent_dispatch(void) { +#ifdef __FreeBSD__ struct kevent changelist[MEVENT_MAX]; struct kevent eventlist[MEVENT_MAX]; struct mevent *pipev; int mfd; int numev; +#else + struct mevent *pipev; + int portfd; +#endif int ret; #ifndef WITHOUT_CAPSICUM cap_rights_t rights; @@ -413,8 +587,13 @@ mevent_dispatch(void) mevent_tid = pthread_self(); mevent_set_name(); +#ifdef __FreeBSD__ mfd = kqueue(); assert(mfd > 0); +#else + portfd = port_create(); + assert(portfd >= 0); +#endif #ifndef WITHOUT_CAPSICUM cap_rights_init(&rights, CAP_KQUEUE); @@ -448,6 +627,7 @@ mevent_dispatch(void) assert(pipev != NULL); for (;;) { +#ifdef __FreeBSD__ /* * Build changelist if required. * XXX the changelist can be put into the blocking call @@ -474,5 +654,22 @@ mevent_dispatch(void) * Handle reported events */ mevent_handle(eventlist, ret); + +#else /* __FreeBSD__ */ + port_event_t pev; + + /* Handle any pending updates */ + mevent_update_pending(portfd); + + /* Block awaiting events */ + ret = port_get(portfd, &pev, NULL); + if (ret != 0 && errno != EINTR) { + perror("Error return from port_get"); + continue; + } + + /* Handle reported event */ + mevent_handle_pe(&pev); +#endif /* __FreeBSD__ */ } } diff --git a/usr/src/cmd/bhyve/mevent_test.c b/usr/src/cmd/bhyve/mevent_test.c index 9c68ff7874..a57aceec55 100644 --- a/usr/src/cmd/bhyve/mevent_test.c +++ b/usr/src/cmd/bhyve/mevent_test.c @@ -27,6 +27,10 @@ */ /* + * Copyright 2018 Joyent, Inc. + */ + +/* * Test program for the micro event library. Set up a simple TCP echo * service. * @@ -35,10 +39,14 @@ #include <sys/types.h> #include <sys/stdint.h> +#ifdef __FreeBSD__ #include <sys/sysctl.h> +#endif #include <sys/socket.h> #include <netinet/in.h> +#ifdef __FreeBSD__ #include <machine/cpufunc.h> +#endif #include <stdio.h> #include <stdlib.h> @@ -66,20 +74,29 @@ uint64_t tevbuf[TEVSZ]; static void timer_print(void) { - uint64_t min, max, diff, sum, tsc_freq; + uint64_t min, max, diff, sum; +#ifdef __FreeBSD__ + uint64_t tsc_freq; size_t len; +#endif int j; min = UINT64_MAX; max = 0; sum = 0; +#ifdef __FreeBSD__ len = sizeof(tsc_freq); sysctlbyname("machdep.tsc_freq", &tsc_freq, &len, NULL, 0); +#endif for (j = 1; j < TEVSZ; j++) { +#ifdef __FreeBSD__ /* Convert a tsc diff into microseconds */ diff = (tevbuf[j] - tevbuf[j-1]) * 1000000 / tsc_freq; +#else + diff = (tevbuf[j] - tevbuf[j-1]) / 1000; +#endif sum += diff; if (min > diff) min = diff; @@ -99,7 +116,11 @@ timer_callback(int fd, enum ev_type type, void *param) if (i >= TEVSZ) abort(); +#ifdef __FreeBSD__ tevbuf[i++] = rdtsc(); +#else + tevbuf[i++] = gethrtime(); +#endif if (i == TEVSZ) { mevent_delete(tevp); @@ -195,14 +216,15 @@ acceptor(void *param) pthread_t tid; int news; int s; - static int first; if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } +#ifdef __FreeBSD__ sin.sin_len = sizeof(sin); +#endif sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(TEST_PORT); @@ -246,6 +268,7 @@ acceptor(void *param) return (NULL); } +int main() { pthread_t tid; @@ -253,4 +276,5 @@ main() pthread_create(&tid, NULL, acceptor, NULL); mevent_dispatch(); + return (0); } diff --git a/usr/src/cmd/bhyve/test/Makefile b/usr/src/cmd/bhyve/test/Makefile new file mode 100644 index 0000000000..7dbee0c5f3 --- /dev/null +++ b/usr/src/cmd/bhyve/test/Makefile @@ -0,0 +1,18 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +SUBDIRS = scripts tst + +include Makefile.subdirs diff --git a/usr/src/cmd/bhyve/test/Makefile.com b/usr/src/cmd/bhyve/test/Makefile.com new file mode 100644 index 0000000000..3c719bcea7 --- /dev/null +++ b/usr/src/cmd/bhyve/test/Makefile.com @@ -0,0 +1,60 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +include $(SRC)/Makefile.master +include $(SRC)/cmd/Makefile.cmd +include $(SRC)/cmd/Makefile.cmd.64 + +# +# Force c99 for everything +# +CSTD= $(CSTD_GNU99) +C99MODE= -xc99=%all +C99LMODE= -Xc99=%all + +CFLAGS += $(CCVERBOSE) -_gcc=-Wimplicit-function-declaration \ + -_gcc=-Wno-parentheses +CFLAGS64 += $(CCVERBOSE) -_gcc=-Wimplicit-function-declaration \ + -_gcc=-Wno-parentheses +CPPFLAGS = -I$(SRC)/cmd/bhyve \ + -I$(COMPAT)/freebsd -I$(CONTRIB)/freebsd \ + -I$(CONTRIB)/freebsd/dev/usb/controller \ + -I$(CONTRIB)/freebsd/dev/mii \ + $(CPPFLAGS.master) \ + -I$(ROOT)/usr/platform/i86pc/include \ + -I$(SRC)/uts/i86pc/io/vmm \ + -I$(SRC)/uts/common \ + -I$(SRC)/uts/i86pc \ + -I$(SRC)/lib/libdladm/common \ + -DWITHOUT_CAPSICUM +CPPFLAGS += -I$(COMPAT)/freebsd/amd64 -I$(CONTRIB)/freebsd/amd64 + +CLEANFILES += $(EXETESTS) +CLOBBERFILES += $(ROOTTESTS) + +# +# Install related definitions +# +ROOTOPTPKG = $(ROOT)/opt/bhyvetest +ROOTBIN = $(ROOTOPTPKG)/bin +ROOTTST = $(ROOTOPTPKG)/tst +ROOTTSTDIR = $(ROOTTST)/$(TSTDIR) +ROOTTSTEXES = $(EXETESTS:%=$(ROOTTSTDIR)/%) +ROOTTSTSH = $(SHTESTS:%=$(ROOTTSTDIR)/%) +ROOTOUT = $(OUTFILES:%=$(ROOTTSTDIR)/%) +ROOTTESTS = $(ROOTTSTEXES) $(ROOTTSTSH) $(ROOTOUT) +FILEMODE = 0555 +LDLIBS = $(LDLIBS.cmd) +LINTEXE = $(EXETESTS:%.exe=%.exe.ln) diff --git a/usr/src/cmd/bhyve/test/Makefile.subdirs b/usr/src/cmd/bhyve/test/Makefile.subdirs new file mode 100644 index 0000000000..45f0aa67fa --- /dev/null +++ b/usr/src/cmd/bhyve/test/Makefile.subdirs @@ -0,0 +1,29 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +.KEEP_STATE: + +all := TARGET += all +clean := TARGET += clean +clobber := TARGET += clobber +install := TARGET += install +lint := TARGET += lint + +all clean clobber install lint: $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: diff --git a/usr/src/cmd/bhyve/test/Makefile.targ b/usr/src/cmd/bhyve/test/Makefile.targ new file mode 100644 index 0000000000..e3ec55cfdb --- /dev/null +++ b/usr/src/cmd/bhyve/test/Makefile.targ @@ -0,0 +1,55 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +$(ROOTOPTPKG): + $(INS.dir) + +$(ROOTBIN): $(ROOTOPTPKG) + $(INS.dir) + +$(ROOTBIN)/%: %.ksh $(ROOTBIN) + $(INS.rename) + +$(ROOTTST): $(ROOTOPTPKG) + $(INS.dir) + +$(ROOTTSTDIR): $(ROOTTST) + $(INS.dir) + +$(ROOTTSTDIR)/%.ksh: %.ksh $(ROOTTSTDIR) + $(INS.file) + +$(ROOTTSTDIR)/%.out: %.out $(ROOTTSTDIR) + $(INS.file) + +%.exe: %.o $(SUPOBJS) + $(LINK.c) -o $@ $< $(SUPOBJS) $(LDLIBS) + $(POST_PROCESS) + +$(ROOTTSTDIR)/%.exe: %.exe $(ROOTTSTDIR) + $(INS.file) + +all: install + +%.exe.ln: %.c $(SUPOBJS) + $(LINT.c) $< $(LDLIBS) + +lint: $(LINTEXE) + +clean: + -$(RM) *.o $(CLEANFILES) + +clobber: clean + -$(RM) $(CLOBBERFILES) diff --git a/usr/src/cmd/bhyve/test/scripts/Makefile b/usr/src/cmd/bhyve/test/scripts/Makefile new file mode 100644 index 0000000000..d28a5edb8f --- /dev/null +++ b/usr/src/cmd/bhyve/test/scripts/Makefile @@ -0,0 +1,28 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +include ../Makefile.com + +SRCS = bhyvetest +SCRIPTS = $(SRCS:%=$(ROOTBIN)/%) + +SCRIPTS := FILEMODE = 0555 +CLOBBERFILES = $(SCRIPTS) + +install: $(SCRIPTS) + +lint: + +include ../Makefile.targ diff --git a/usr/src/cmd/bhyve/test/scripts/bhyvetest.ksh b/usr/src/cmd/bhyve/test/scripts/bhyvetest.ksh new file mode 100644 index 0000000000..95b7743417 --- /dev/null +++ b/usr/src/cmd/bhyve/test/scripts/bhyvetest.ksh @@ -0,0 +1,231 @@ +#!/bin/ksh +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +# +# bhyve test suite driver +# +unalias -a + +bt_arg0=$(basename $0) +bt_root="$(cd $(dirname $0)/..; pwd -P)" +bt_ksh="/usr/bin/ksh" +bt_outdir= +bt_keep= +bt_all= +bt_tnum=0 +bt_tfail=0 +bt_tsuc=0 + +function usage +{ + typeset msg="$*" + [[ -z "$msg" ]] || echo "$msg" 2>&1 + cat <<USAGE >&2 +Usage: $bt_arg0 [ -o dir ] [ -k ] [ -a | test ... ] + + -o dir Sets 'dir' as the output directory + -a Runs all tests, ignores tests passed in + -k Keep output from all tests, not just failures + -m mdb binary to test +USAGE + exit 2 +} + +function fatal +{ + typeset msg="$*" + [[ -z "$msg" ]] && msg="failed" + echo "$bt_arg0: $msg" >&2 + exit 1 +} + +function setup_outdir +{ + bt_outdir="$bt_outdir/$bt_arg0.$$" + mkdir -p $bt_outdir || fatal "failed to make output dir $bt_outdir" +} + +function run_single +{ + typeset name=$1 + typeset expect base ext exe command odir res reason + typeset iserr + + [[ -z "$name" ]] && fail "missing test to run" + base=${name##*/} + ext=${base##*.} + expect=${base%%.*} + odir="$bt_outdir/current" + [[ -z "$ext" ]] && fatal "found test without ext: $name" + [[ -z "$expect" ]] && fatal "found test without prefix: $name" + + if [[ "$expect" == "err" || "$expect" == "ecreate" ]]; then + iserr="yup" + else + iserr="" + fi + + case "$ext" in + "ksh") + command="$bt_ksh ./$base" + ;; + "exe") + command="./$base" + ;; + "out") + # + # This is the file format for checking output against. + # + return 0 + ;; + *) + echo "skipping test $name (unknown extensino)" + return 0 + ;; + esac + + echo "Executing test $name ... \c" + mkdir -p "$odir" >/dev/null || fatal "can't make output directory" + cd $(dirname $name) || fatal "failed to enter test directory" + $command > "$odir/stdout" 2>"$odir/stderr" + res=$? + cd - > /dev/null || fatal "failed to leave test directory" + + if [[ -f "$name.out" ]] && \ + ! diff "$name.out" "$odir/stdout" >/dev/null; then + cp $name.out $odir/$base.out + reason="stdout mismatch" + elif [[ -n "$iserr" && $res -eq 0 ]]; then + reason="test exited $res, not non-zero" + elif [[ -z "$iserr" && $res -ne 0 ]]; then + reason="test exited $res, not zero" + fi + + if [[ -n "$reason" ]]; then + echo "$reason" + ((bt_tfail++)) + mv "$odir" "$bt_outdir/failure.$bt_tfail" || fatal \ + "failed to move test output directory" + cp "$name" "$bt_outdir/failure.$bt_tfail/$(basename $name)" || \ + fatal "failed to copy test into output directory" + else + echo "passed" + ((bt_tsuc++)) + mv "$odir" "$bt_outdir/success.$bt_tsuc" || fatal \ + "failed to move test directory" + fi + + ((bt_tnum++)) +} + +function run_all +{ + typeset tests t dir + + tests=$(ls -1 $bt_root/tst/*/*.@(ksh|exe)) + for t in $tests; do + run_single $t + done +} + +function welcome +{ + cat <<WELCOME +Starting tests... +output directory: $bt_outdir +WELCOME +} + +function cleanup +{ + [[ -n "$bt_keep" ]] && return + rm -rf "$bt_outdir"/success.* || fatal \ + "failed to remove successful test cases" + if [[ $bt_tfail -eq 0 ]]; then + rmdir "$bt_outdir" || fatal \ + "failed to remove test output directory" + fi +} + +function goodbye +{ + cat <<EOF + +------------- +Results +------------- + +Tests passed: $bt_tsuc +Tests failed: $bt_tfail +Tests ran: $bt_tnum + +EOF + if [[ $bt_tfail -eq 0 ]]; then + echo "Congrats, some tiny parts of bhyve aren't completely" \ + "broken, the tests pass". + else + echo "Some tests failed, you have some work to do." + fi +} + +while getopts ":ahko:m:" c $@; do + case "$c" in + a) + bt_all="y" + ;; + k) + bt_keep="y" + ;; + o) + bt_outdir="$OPTARG" + ;; + h) + usage + ;; + :) + usage "option requires an argument -- $OPTARG" + ;; + *) + usage "invalid option -- $OPTARG" + ;; + esac +done + +shift $((OPTIND-1)) + +[[ -z "$bt_all" && $# == 0 ]] && usage "no tests to run" + +[[ -z "$bt_outdir" ]] && bt_outdir="$PWD" + +setup_outdir +welcome + +if [[ ! -z "$bt_all" ]]; then + run_all +else + for t in $@; do + [[ -f $t ]] || fatal "cannot find test $t" + run_single $t + done +fi + +goodbye +cleanup + +# +# Exit 1 if we have tests that return non-zero +# +[[ $bt_tfai -eq 0 ]] diff --git a/usr/src/cmd/bhyve/test/tst/Makefile b/usr/src/cmd/bhyve/test/tst/Makefile new file mode 100644 index 0000000000..f6a6ec96fc --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/Makefile @@ -0,0 +1,18 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +SUBDIRS = mevent + +include ../Makefile.subdirs diff --git a/usr/src/cmd/bhyve/test/tst/mevent/Makefile b/usr/src/cmd/bhyve/test/tst/mevent/Makefile new file mode 100644 index 0000000000..047886bc6a --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/Makefile @@ -0,0 +1,30 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018 Joyent, Inc. +# + +TSTDIR = mevent +EXETESTS = \ + lists.delete.exe \ + read.disable.exe \ + read.pause.exe \ + read.requeue.exe \ + +SHTESTS = +SUPOBJS = mevent.o testlib.o + +include ../../Makefile.com + +install: $(ROOTTESTS) + +include ../../Makefile.targ diff --git a/usr/src/cmd/bhyve/test/tst/mevent/lists.delete.c b/usr/src/cmd/bhyve/test/tst/mevent/lists.delete.c new file mode 100644 index 0000000000..d09ac133a3 --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/lists.delete.c @@ -0,0 +1,172 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +/* + * Test: lists.delete + * Assertion: mevent_delete() causes the total number of events to decrease + * + * Strategy: 1. Create a pipe. + * 2. Call mevent_add() to be notified of writes to the pipe. The + * callback will do nothing other than generate an error if it + * is called. + * 3. Create another pipe and add a read event watcher to it. The + * callback will signal a cv when called. A write to the pipe + * followed by a wait on the cv will ensure that async + * operations in mevent.c are complete. See flush_and_wait(). + * 4. Call flush_and_wait(), then get event count. + * 5. Delete the event created in step 2. + * 6. Call flush_and_wait(), then get event count. + * 7. Verify result in step 6 is one less than result in step 4. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "testlib.h" +#include "mevent.h" + +static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +static int +get_count(void) +{ + int global = -1, change = -1, del_pending = -1; + int total; + + test_mevent_count_lists(&global, &change, &del_pending); + ASSERT_INT_NEQ(("count not set"), global, -1); + ASSERT_INT_NEQ(("count not set"), change, -1); + ASSERT_INT_NEQ(("count not set"), change, -1); + ASSERT_INT_EQ(("pending delete not processed"), del_pending, 0); + + total = global + change + del_pending; + + VERBOSE(("count = %d (%d + %d + %d)", total, global, change, + del_pending)); + + return (total); +} + +static void +not_called_cb(int fd, enum ev_type ev, void *arg) +{ + FAIL(("this callback should never be called")); +} + +static void +flush_cb(int fd, enum ev_type ev, void *arg) +{ + char buf[32]; + + /* Drain the pipe */ + while (read(fd, buf, sizeof (buf)) > 0) + ; + + pthread_mutex_lock(&mtx); + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mtx); +} + +void +flush_and_wait(int fd) +{ + uint8_t msg = 42; + + /* + * Lock taken ahead of waking flush_cb so this thread doesn't race + * with the event thread. + */ + pthread_mutex_lock(&mtx); + if (write(fd, &msg, sizeof (msg)) != sizeof (msg)) { + FAIL(("bad write")); + } + + /* Wait for it to be read */ + pthread_cond_wait(&cv, &mtx); + pthread_mutex_unlock(&mtx); +} + +int +main(int argc, const char *argv[]) +{ + int unused_pipe[2]; + int flush_pipe[2]; + struct mevent *unused_evp, *flush_evp; + int count1, count2; + + start_test(argv[0], 5); + start_event_thread(); + + /* + * Create first pipe and related event + */ + if (pipe(unused_pipe) != 0) { + FAIL_ERRNO("pipe"); + } + VERBOSE(("unused_pipe[] = { %d, %d }", unused_pipe[0], unused_pipe[1])); + if (fcntl(unused_pipe[0], F_SETFL, O_NONBLOCK) != 0) { + FAIL_ERRNO("set pipe nonblocking"); + } + unused_evp = mevent_add(unused_pipe[0], EVF_READ, not_called_cb, NULL); + ASSERT_PTR_NEQ(("mevent_add"), unused_evp, NULL); + + /* + * Create flush pipe and related event + */ + if (pipe(flush_pipe) != 0) { + FAIL_ERRNO("pipe"); + } + VERBOSE(("flush_pipe[] = { %d, %d }", flush_pipe[0], + flush_pipe[1])); + if (fcntl(flush_pipe[0], F_SETFL, O_NONBLOCK) != 0) { + FAIL_ERRNO("set pipe nonblocking"); + } + flush_evp = mevent_add(flush_pipe[0], EVF_READ, flush_cb, NULL); + ASSERT_PTR_NEQ(("mevent_add"), flush_evp, NULL); + + /* Get count before delete. */ + flush_and_wait(flush_pipe[1]); + count1 = get_count(); + + /* + * Delete the first event and flush a read after the delete is + * complete. + */ + if (mevent_delete(unused_evp) != 0) { + FAIL_ERRNO("mevent_delete"); + } + + /* + * Verify count decreased. + */ + flush_and_wait(flush_pipe[1]); + count2 = get_count(); + if (count1 - 1 != count2 ) { + FAIL(("mevent_delete() did not decrease count by 1: " + "was %d, now %d", count1, count2)); + } + + PASS(); +} diff --git a/usr/src/cmd/bhyve/test/tst/mevent/mevent.c b/usr/src/cmd/bhyve/test/tst/mevent/mevent.c new file mode 100644 index 0000000000..17b6546847 --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/mevent.c @@ -0,0 +1,57 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +#include "../../../mevent.c" +#include "testlib.h" + +/* + * Returns by reference the number of events on the global and change lists. + * + * Used by tests that wish to ensure that the event count changes as suggested + * by mevent_add() and mevent_delete(). Note that a delete does not immediately + * delete an event. Events that are pending delete are included in the change + * list until the next pass through the change list to process pending changes. + */ +void +test_mevent_count_lists(int *ret_global, int *ret_change, int *ret_del_pending) +{ + struct mevent *mevp; + int global = 0; + int change = 0; + int del_pending = 0; + + mevent_qlock(); + + LIST_FOREACH(mevp, &global_head, me_list) { + global++; + VERBOSE(("on global: type %d fd %d state %d", mevp->me_type, + mevp->me_fd, mevp->me_state)); + } + + LIST_FOREACH(mevp, &change_head, me_list) { + change++; + if (mevp->me_state == MEV_DEL_PENDING) { + del_pending++; + } + VERBOSE(("on change: type %d fd %d state %d", mevp->me_type, + mevp->me_fd, mevp->me_state)); + } + + mevent_qunlock(); + + *ret_global = global; + *ret_change = change; + *ret_del_pending = del_pending; +} diff --git a/usr/src/cmd/bhyve/test/tst/mevent/read.disable.c b/usr/src/cmd/bhyve/test/tst/mevent/read.disable.c new file mode 100644 index 0000000000..d23b1af96c --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/read.disable.c @@ -0,0 +1,163 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +/* + * Test: read.cancel + * Assertion: A read is not requeued if mevent_disable() is called while it is + * being handled. + * + * Strategy: 1. Create a pipe + * 2. Call mevent_add() to be notified of writes to the pipe. The + * callback will signal a cv. + * 3. Write to the pipe then wait for a wakeup. + * 4. From the read event callback, disable the event then awaken + * the main thread. + * 5. In the main thread, add a timer event that will awaken the + * main thread after a short delay. + * 5. Write to the pipe and wait to be awoken. The wakeup should + * come from the timer event, not the read event. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "testlib.h" +#include "mevent.h" + +typedef enum { + CB_NONE, + CB_READ, + CB_TIMER, +} lastwake_t; + +static lastwake_t lastwake = CB_NONE; + +static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +static struct mevent *read_event; + +static void +munch(int fd, enum ev_type ev, void *arg) +{ + ssize_t nbytes; + char buf[32] = { 0 }; + int err; + + if ((nbytes = read(fd, buf, sizeof (buf))) < 0) { + FAIL_ERRNO("bad read"); + } + VERBOSE(("read %ld bytes '%s'", nbytes, buf)); + + err = mevent_disable(read_event); + ASSERT_INT_EQ(("mevent_disable: ", strerror(err)), err, 0); + + pthread_mutex_lock(&mtx); + + ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_NONE); + lastwake = CB_READ; + + pthread_cond_signal(&cv); + VERBOSE(("wakeup")); + + pthread_mutex_unlock(&mtx); +} + +static void +tick(int ms, enum ev_type ev, void *arg) +{ + pthread_mutex_lock(&mtx); + + ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_READ); + lastwake = CB_TIMER; + + pthread_cond_signal(&cv); + VERBOSE(("wakeup")); + + pthread_mutex_unlock(&mtx); +} + +int +main(int argc, const char *argv[]) +{ + int pipefds[2]; + struct mevent *timer; + ssize_t written; + char *msgs[] = { "first", "second" }; + char *msg; + + start_test(argv[0], 5); + start_event_thread(); + + if (pipe(pipefds) != 0) { + FAIL_ERRNO("pipe"); + } + if (fcntl(pipefds[0], F_SETFL, O_NONBLOCK) != 0) { + FAIL_ERRNO("set pipe nonblocking"); + } + + /* + * First write + */ + msg = msgs[0]; + read_event = mevent_add(pipefds[0], EVF_READ, munch, msg); + ASSERT_PTR_NEQ(("mevent_add pipefd"), read_event, NULL); + + pthread_mutex_lock(&mtx); + written = write(pipefds[1], msg, strlen(msg)); + if (written < 0) { + FAIL_ERRNO("bad write"); + } + ASSERT_INT64_EQ(("write '%s' failed", msg), written, strlen(msg)); + + /* + * Wait for it to be read + */ + pthread_cond_wait(&cv, &mtx); + ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_READ); + pthread_mutex_unlock(&mtx); + + /* + * Add timer, second write. + */ + msg = msgs[1]; + timer = mevent_add(50, EVF_TIMER, tick, msg); + ASSERT_PTR_NEQ(("mevent_add timer"), timer, NULL); + + pthread_mutex_lock(&mtx); + written = write(pipefds[1], msg, strlen(msg)); + if (written < 0) { + FAIL_ERRNO("bad write"); + } + ASSERT_INT64_EQ(("write '%s' failed", msg), written, strlen(msg)); + + /* + * Wait for timer to expire + */ + pthread_cond_wait(&cv, &mtx); + ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_TIMER); + pthread_mutex_unlock(&mtx); + + PASS(); +} diff --git a/usr/src/cmd/bhyve/test/tst/mevent/read.pause.c b/usr/src/cmd/bhyve/test/tst/mevent/read.pause.c new file mode 100644 index 0000000000..c877f014f6 --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/read.pause.c @@ -0,0 +1,152 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +/* + * Test: read.pause + * Assertion: mevent_disable() can be used to pause reads. + * + * Strategy: 1. Create a pipe + * 2. Call mevent_add() to be notified of writes to the pipe. The + * callback will signal a cv. + * 3. In a loop, write to the pipe then wait on the cv. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "testlib.h" +#include "mevent.h" + +static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +static char cookie[] = "Chocolate chip with fudge stripes"; + +/* + * After this many bytes are sent, writes will get batched up, progress will be + * made on the write side via an interval timer + */ +const int pauseat = 8; + +static void +munch(int fd, enum ev_type ev, void *arg) +{ + static int i = 0; + char buf[sizeof (cookie)] = { 0 }; + ssize_t nbytes; + ssize_t expected; + + ASSERT_INT_EQ(("bad event"), ev, EVF_READ); + ASSERT_PTR_EQ(("bad cookie"), arg, cookie); + + /* + * For the first while, expect data to come a byte at a time. After the + * pause, we should get a burst with the rest of the data. + */ + if (i > pauseat) { + expected = strlen(cookie) - pauseat - 1; + } else { + expected = 1; + } + + if ((nbytes = read(fd, buf, sizeof (buf))) < 0) { + FAIL_ERRNO("bad read"); + } + VERBOSE(("read %ld bytes '%s'", nbytes, buf)); + + ASSERT_INT64_EQ(("wanted a byte of cookie"), nbytes, expected); + + if (expected == 1) { + ASSERT_CHAR_EQ(("bad byte %d of cookie", i), buf[0], cookie[i]); + } else { + ASSERT_STR_EQ(("bad last half of cookie"), buf, &cookie[i]); + } + + pthread_mutex_lock(&mtx); + pthread_cond_signal(&cv); + VERBOSE(("wakeup")); + pthread_mutex_unlock(&mtx); + + i++; +} + +static void +tick(int ms, enum ev_type ev, void *arg) +{ + pthread_mutex_lock(&mtx); + pthread_cond_signal(&cv); + VERBOSE(("wakeup")); + pthread_mutex_unlock(&mtx); +} + +int +main(int argc, const char *argv[]) +{ + int pipefds[2]; + struct mevent *evp, *timer; + ssize_t written; + + start_test(argv[0], 5); + start_event_thread(); + + if (pipe(pipefds) != 0) { + FAIL_ERRNO("pipe"); + } + if (fcntl(pipefds[0], F_SETFL, O_NONBLOCK) != 0) { + FAIL_ERRNO("set pipe nonblocking"); + } + + evp = mevent_add(pipefds[0], EVF_READ, munch, cookie); + ASSERT_PTR_NEQ(("mevent_add pipefd"), evp, NULL); + + for (int i = 0; cookie[i] != 0; i++) { + pthread_mutex_lock(&mtx); + written = write(pipefds[1], cookie + i, 1); + if (written < 0) { + FAIL_ERRNO("bad write"); + } + ASSERT_INT64_EQ(("write byte %d of cookie", i), written, 1); + + /* Wait for it to be read */ + pthread_cond_wait(&cv, &mtx); + pthread_mutex_unlock(&mtx); + + if (i == pauseat) { + timer = mevent_add(10, EVF_TIMER, tick, + &cookie[pauseat]); + ASSERT_PTR_NEQ(("mevent_add timer"), timer, NULL); + VERBOSE(("disable munch")); + mevent_disable(evp); + } + } + + pthread_mutex_lock(&mtx); + + mevent_enable(evp); + + pthread_cond_wait(&cv, &mtx); + pthread_mutex_unlock(&mtx); + + PASS(); +} diff --git a/usr/src/cmd/bhyve/test/tst/mevent/read.requeue.c b/usr/src/cmd/bhyve/test/tst/mevent/read.requeue.c new file mode 100644 index 0000000000..ddc3e27235 --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/read.requeue.c @@ -0,0 +1,108 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +/* + * Test: read.requeue + * Assertion: A sequence of writes turns into a sequence of events. + * + * Strategy: 1. Create a pipe + * 2. Call mevent_add() to be notified of writes to the pipe. The + * callback will signal a cv. + * 3. In a loop, write to the pipe then wait on the cv. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "testlib.h" +#include "mevent.h" + +static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +static char *cookie = "Chocolate chip with fudge stripes"; + +static void +munch(int fd, enum ev_type ev, void *arg) +{ + static int i = 0; + char buf[8] = { 0 }; + ssize_t nbytes; + + ASSERT_INT_EQ(("bad event"), ev, EVF_READ); + ASSERT_PTR_EQ(("bad cookie"), arg, cookie); + + if ((nbytes = read(fd, buf, sizeof (buf))) < 0) { + ASSERT_INT64_EQ(("bad read: %s", strerror(errno)), nbytes, 1); + } + VERBOSE(("read %ld bytes '%s'", nbytes, buf)); + + ASSERT_INT64_EQ(("wanted a byte of cookie"), nbytes, 1); + + ASSERT_CHAR_EQ(("bad byte %d of cookie", i), buf[0], cookie[i]); + + pthread_mutex_lock(&mtx); + pthread_cond_signal(&cv); + VERBOSE(("wakeup")); + pthread_mutex_unlock(&mtx); + + i++; +} + +int +main(int argc, const char *argv[]) +{ + int pipefds[2]; + struct mevent *evp; + + start_test(argv[0], 5); + start_event_thread(); + + if (pipe(pipefds) != 0) { + FAIL_ERRNO("pipe"); + } + if (fcntl(pipefds[0], F_SETFL, O_NONBLOCK) != 0) { + FAIL_ERRNO("set pipe nonblocking"); + } + + evp = mevent_add(pipefds[0], EVF_READ, munch, cookie); + ASSERT_PTR_NEQ(("mevent_add"), evp, NULL); + + for (int i = 0; cookie[i] != '\0'; i++) { + ssize_t written; + + pthread_mutex_lock(&mtx); + written = write(pipefds[1], cookie + i, 1); + if (written < 0) { + FAIL_ERRNO("bad write"); + } + ASSERT_INT64_EQ(("write byte %d of cookie", i), written, 1); + + /* Wait for it to be read */ + pthread_cond_wait(&cv, &mtx); + pthread_mutex_unlock(&mtx); + } + + PASS(); +} diff --git a/usr/src/cmd/bhyve/test/tst/mevent/testlib.c b/usr/src/cmd/bhyve/test/tst/mevent/testlib.c new file mode 100644 index 0000000000..af756d1509 --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/testlib.c @@ -0,0 +1,69 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +#include <pthread.h> +#include <signal.h> +#include <strings.h> +#include <unistd.h> + +#include "testlib.h" +#include "mevent.h" + +const char *testlib_prog; +boolean_t testlib_verbose; + +static void +timed_out(int signo) { + ASSERT_INT_EQ(("timeout signal"), signo, SIGALRM); + + FAIL(("Timed out")); +} + +void +start_test(const char *argv0, uint32_t timeout) +{ + char *val; + + testlib_prog = strrchr(argv0, '/'); + if (testlib_prog == NULL) { + testlib_prog = argv0; + } else { + testlib_prog++; + } + + testlib_verbose = ((val = getenv("TEST_VERBOSE")) != NULL) && + val[0] != '\0'; + + signal(SIGALRM, timed_out); + alarm(timeout); +} + +/* ARGSUSED */ +static void * +event_thread(void *arg) +{ + mevent_dispatch(); + return (NULL); +} + +void +start_event_thread(void) +{ + pthread_t tid; + + if (pthread_create(&tid, NULL, event_thread, NULL) != 0) { + FAIL_ERRNO("pthread_create"); + } +} diff --git a/usr/src/cmd/bhyve/test/tst/mevent/testlib.h b/usr/src/cmd/bhyve/test/tst/mevent/testlib.h new file mode 100644 index 0000000000..80949f3cc7 --- /dev/null +++ b/usr/src/cmd/bhyve/test/tst/mevent/testlib.h @@ -0,0 +1,88 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Joyent, Inc. + */ + +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "mevent.h" + +#define EXIT_PASS 0 +#define EXIT_FAIL 1 + +#define VERBOSE(msg) \ + if (testlib_verbose) { \ + (void) printf("VERBOSE %s: %s:%d %s: ", testlib_prog, \ + __FILE__, __LINE__, __func__); \ + (void) printf msg; \ + (void) printf("\n"); \ + } + +#define FAIL_PROLOGUE() \ + (void) printf("FAIL %s: %s:%d: ", testlib_prog, __FILE__, __LINE__) + +#define FAIL(msg) \ + { \ + FAIL_PROLOGUE(); \ + (void) printf msg; \ + (void) printf("\n"); \ + exit(EXIT_FAIL); \ + } + +#define FAIL_ERRNO(msg) FAIL((msg ": %s", strerror(errno))) + +#define PASS() \ + { \ + (void) printf("PASS %s\n", testlib_prog); \ + exit(EXIT_PASS); \ + } + +#define ASSERT_CMP(msg, got, cmp, exp, nfmt) \ + if (!(got cmp exp)) { \ + FAIL_PROLOGUE(); \ + (void) printf msg; \ + (void) printf(": %s=" nfmt " %s %s=" nfmt "\n", \ + #got, got, #cmp, #exp, exp); \ + exit(EXIT_FAIL); \ + } + +#define ASSERT_CHAR_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%c") +#define ASSERT_INT_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%d") +#define ASSERT_INT_NEQ(msg, got, exp) ASSERT_CMP(msg, got, !=, exp, "%d") +#define ASSERT_INT64_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%ld") +#define ASSERT_PTR_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%p") +#define ASSERT_PTR_NEQ(msg, got, exp) ASSERT_CMP(msg, got, !=, exp, "%p") + +#define ASSERT_STR_EQ(msg, got, exp) \ + if (strcmp(got, exp) != 0) { \ + FAIL_PROLOGUE(); \ + (void) printf msg; \ + (void) printf(": %s='%s' != %s='%s'\n", \ + #got, got, #exp, exp); \ + exit(EXIT_FAIL); \ + } + +extern const char *testlib_prog; +extern boolean_t testlib_verbose; + +extern void start_test(const char *, uint32_t); +extern void start_event_thread(void); +extern void test_mevent_count_lists(int *, int *, int *); diff --git a/usr/src/cmd/bhyve/uart_emul.c b/usr/src/cmd/bhyve/uart_emul.c index b1bf2dbfec..167a9bc42a 100644 --- a/usr/src/cmd/bhyve/uart_emul.c +++ b/usr/src/cmd/bhyve/uart_emul.c @@ -26,7 +26,6 @@ * * $FreeBSD$ * - * Copyright (c) 2018, Joyent, Inc. */ /* * This file and its contents are supplied under the terms of the @@ -39,7 +38,7 @@ * http://www.illumos.org/license/CDDL. * * Copyright 2015 Pluribus Networks Inc. - * Copyright 2017 Joyent, Inc. + * Copyright 2018 Joyent, Inc. */ #include <sys/cdefs.h> @@ -73,14 +72,7 @@ __FBSDID("$FreeBSD$"); #include <poll.h> #endif -#ifndef __FreeBSD__ -#include <bhyve.h> - -#include "bhyverun.h" -#endif -#ifdef __FreeBSD__ #include "mevent.h" -#endif #include "uart_emul.h" #define COM1_BASE 0x3F8 @@ -134,7 +126,7 @@ struct ttyfd { struct uart_softc { pthread_mutex_t mtx; /* protects all softc elements */ - uint8_t data; /* Data register (R/W) */ + uint8_t data; /* Data register (R/W) */ uint8_t ier; /* Interrupt enable register (R/W) */ uint8_t lcr; /* Line control register (R/W) */ uint8_t mcr; /* Modem control register (R/W) */ @@ -147,9 +139,7 @@ struct uart_softc { uint8_t dlh; /* Baudrate divisor latch MSB */ struct fifo rxfifo; -#ifdef __FreeBSD__ struct mevent *mev; -#endif struct ttyfd tty; #ifndef __FreeBSD__ @@ -167,12 +157,8 @@ struct uart_softc { uart_intr_func_t intr_deassert; }; -#ifdef __FreeBSD__ static void uart_drain(int fd, enum ev_type ev, void *arg); -#else -static void uart_tty_drain(struct uart_softc *sc); static int uart_sock_drain(struct uart_softc *sc); -#endif static void ttyclose(void) @@ -230,9 +216,7 @@ rxfifo_reset(struct uart_softc *sc, int size) char flushbuf[32]; struct fifo *fifo; ssize_t nread; -#ifdef __FreeBSD__ int error; -#endif fifo = &sc->rxfifo; bzero(fifo, sizeof(struct fifo)); @@ -248,14 +232,12 @@ rxfifo_reset(struct uart_softc *sc, int size) break; } -#ifdef __FreeBSD__ /* * Enable mevent to trigger when new characters are available * on the tty fd. */ error = mevent_enable(sc->mev); assert(error == 0); -#endif } } @@ -272,9 +254,7 @@ static int rxfifo_putchar(struct uart_softc *sc, uint8_t ch) { struct fifo *fifo; -#ifdef __FreeBSD__ int error; -#endif fifo = &sc->rxfifo; @@ -284,13 +264,11 @@ rxfifo_putchar(struct uart_softc *sc, uint8_t ch) fifo->num++; if (!rxfifo_available(sc)) { if (sc->tty.opened) { -#ifdef __FreeBSD__ /* * Disable mevent callback if the FIFO is full. */ error = mevent_disable(sc->mev); assert(error == 0); -#endif } } return (0); @@ -302,10 +280,7 @@ static int rxfifo_getchar(struct uart_softc *sc) { struct fifo *fifo; - int c, wasfull; -#ifdef __FreeBSD__ - int error; -#endif + int c, error, wasfull; wasfull = 0; fifo = &sc->rxfifo; @@ -317,10 +292,8 @@ rxfifo_getchar(struct uart_softc *sc) fifo->num--; if (wasfull) { if (sc->tty.opened) { -#ifdef __FreeBSD__ error = mevent_enable(sc->mev); assert(error == 0); -#endif } } return (c); @@ -340,10 +313,8 @@ static void uart_opentty(struct uart_softc *sc) { ttyopen(&sc->tty); -#ifdef __FreeBSD__ sc->mev = mevent_add(sc->tty.fd, EVF_READ, uart_drain, sc); assert(sc->mev != NULL); -#endif } static uint8_t @@ -431,7 +402,6 @@ uart_toggle_intr(struct uart_softc *sc) (*sc->intr_assert)(sc->arg); } -#ifdef __FreeBSD__ static void uart_drain(int fd, enum ev_type ev, void *arg) { @@ -462,30 +432,6 @@ uart_drain(int fd, enum ev_type ev, void *arg) pthread_mutex_unlock(&sc->mtx); } -#else -static void -uart_tty_drain(struct uart_softc *sc) -{ - int ch; - - /* - * Take the softc lock to protect against concurrent - * access from a vCPU i/o exit - */ - pthread_mutex_lock(&sc->mtx); - - if ((sc->mcr & MCR_LOOPBACK) != 0) { - (void) ttyread(&sc->tty); - } else { - while (rxfifo_available(sc) && - ((ch = ttyread(&sc->tty)) != -1)) { - rxfifo_putchar(sc, ch); - } - uart_toggle_intr(sc); - } - - pthread_mutex_unlock(&sc->mtx); -} static int uart_sock_drain(struct uart_softc *sc) @@ -518,9 +464,7 @@ uart_sock_drain(struct uart_softc *sc) break; } - if (rxfifo_available(sc)) { - rxfifo_putchar(sc, ch); - } + rxfifo_putchar(sc, ch); } uart_toggle_intr(sc); } @@ -529,7 +473,6 @@ uart_sock_drain(struct uart_softc *sc) return (ret); } -#endif void uart_write(struct uart_softc *sc, int offset, uint8_t value) @@ -739,29 +682,6 @@ done: } #ifndef __FreeBSD__ -static void * -uart_tty_thread(void *param) -{ - struct uart_softc *sc = param; - pollfd_t pollset; - - pollset.fd = sc->tty.fd; - pollset.events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND; - - for (;;) { - if (poll(&pollset, 1, -1) < 0) { - if (errno != EINTR) { - perror("poll failed"); - break; - } - continue; - } - uart_tty_drain(sc); - } - - return (NULL); -} - static int uart_sock_accept_client(struct uart_softc *sc) { @@ -1036,9 +956,6 @@ uart_sock_backend(struct uart_softc *sc, const char *inopts) int uart_set_backend(struct uart_softc *sc, const char *opts) { -#ifndef __FreeBSD__ - int error; -#endif int retval; #ifndef WITHOUT_CAPSICUM cap_rights_t rights; @@ -1087,13 +1004,8 @@ uart_set_backend(struct uart_softc *sc, const char *opts) } #endif - if (retval == 0) { + if (retval == 0) uart_opentty(sc); -#ifndef __FreeBSD__ - error = pthread_create(NULL, NULL, uart_tty_thread, sc); - assert(error == 0); -#endif - } return (retval); } |