/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Configuration and setup interface to vcc driver. * At intialization time, vntsd opens vcc ctrl port and read initial * configuratioa. It manages console groups, creates the listen thread, * dynamically adds and removes virtual console within a group. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vntsd.h" /* signal all clients that console has been deleted */ boolean_t vntsd_notify_client_cons_del(vntsd_client_t *clientp) { (void) mutex_lock(&clientp->lock); clientp->status |= VNTSD_CLIENT_CONS_DELETED; (void) thr_kill(clientp->cons_tid, SIGUSR1); (void) mutex_unlock(&clientp->lock); return (B_FALSE); } /* free console structure */ static void free_cons(vntsd_cons_t *consp) { assert(consp); (void) mutex_destroy(&consp->lock); (void) cond_destroy(&consp->cvp); if (consp->vcc_fd != -1) (void) close(consp->vcc_fd); free(consp); } /* free group structure */ static void free_group(vntsd_group_t *groupp) { assert(groupp); (void) mutex_destroy(&groupp->lock); (void) cond_destroy(&groupp->cvp); if (groupp->sockfd != -1) (void) close(groupp->sockfd); free(groupp); } /* * all clients connected to a console must disconnect before * removing a console. */ static void cleanup_cons(vntsd_cons_t *consp) { vntsd_group_t *groupp; timestruc_t to; assert(consp); D1(stderr, "t@%d vntsd_disconn_clients@%d\n", thr_self(), consp->cons_no); groupp = consp->group; assert(groupp); (void) mutex_lock(&consp->lock); /* wait for all clients disconnect from the console */ while (consp->clientpq != NULL) { consp->status |= VNTSD_CONS_SIG_WAIT; /* signal client to disconnect the console */ (void) vntsd_que_walk(consp->clientpq, (el_func_t)vntsd_notify_client_cons_del); (void) thr_kill(consp->wr_tid, SIGUSR1); to.tv_sec = VNTSD_CV_WAIT_DELTIME; to.tv_nsec = 0; /* wait for clients to disconnect */ (void) cond_reltimedwait(&consp->cvp, &consp->lock, &to); } /* reduce console count in the group */ (void) mutex_lock(&groupp->lock); assert(groupp->num_cons > 0); groupp->num_cons--; (void) mutex_unlock(&groupp->lock); (void) mutex_unlock(&consp->lock); free_cons(consp); } /* search for a group whose console is being deleted */ static boolean_t find_clean_cons_group(vntsd_group_t *groupp) { if (groupp->status & VNTSD_GROUP_CLEAN_CONS) { return (B_TRUE); } else { return (B_FALSE); } } /* search for a console that is being deleted */ static boolean_t find_clean_cons(vntsd_cons_t *consp) { if (consp->status & VNTSD_CONS_DELETED) { return (B_TRUE); } else { return (B_FALSE); } } /* delete a console */ void vntsd_delete_cons(vntsd_t *vntsdp) { vntsd_group_t *groupp; vntsd_cons_t *consp; for (; ; ) { /* get the group contains deleted console */ (void) mutex_lock(&vntsdp->lock); groupp = vntsd_que_walk(vntsdp->grouppq, (el_func_t)find_clean_cons_group); if (groupp == NULL) { /* no more group has console deleted */ (void) mutex_unlock(&vntsdp->lock); return; } (void) mutex_lock(&groupp->lock); groupp->status &= ~VNTSD_GROUP_CLEAN_CONS; (void) mutex_unlock(&groupp->lock); (void) mutex_unlock(&vntsdp->lock); for (; ; ) { /* get the console to be deleted */ (void) mutex_lock(&groupp->lock); /* clean up any deleted console in the group */ if (groupp->conspq != NULL) { consp = vntsd_que_walk(groupp->conspq, (el_func_t)find_clean_cons); if (consp == NULL) { /* no more cons to delete */ (void) mutex_unlock(&groupp->lock); break; } /* remove console from the group */ (void) vntsd_que_rm(&groupp->conspq, consp); (void) mutex_unlock(&groupp->lock); /* clean up the console */ cleanup_cons(consp); } /* delete group? */ if (groupp->conspq == NULL) { /* no more console in the group delete group */ assert(groupp->vntsd); (void) mutex_lock(&groupp->vntsd->lock); (void) vntsd_que_rm(&groupp->vntsd->grouppq, groupp); (void) mutex_unlock(&groupp->vntsd->lock); /* clean up the group */ vntsd_clean_group(groupp); break; } } } } /* clean up a group */ void vntsd_clean_group(vntsd_group_t *groupp) { timestruc_t to; D1(stderr, "t@%d clean_group() group=%s tcp=%lld\n", thr_self(), groupp->group_name, groupp->tcp_port); (void) mutex_lock(&groupp->lock); /* prevent from reentry */ if (groupp->status & VNTSD_GROUP_IN_CLEANUP) { (void) mutex_unlock(&groupp->lock); return; } groupp->status |= VNTSD_GROUP_IN_CLEANUP; /* mark group waiting for listen thread to exits */ groupp->status |= VNTSD_GROUP_SIG_WAIT; (void) mutex_unlock(&groupp->lock); vntsd_free_que(&groupp->conspq, (clean_func_t)cleanup_cons); (void) mutex_lock(&groupp->lock); /* walk through no cons client queue */ while (groupp->no_cons_clientpq != NULL) { (void) vntsd_que_walk(groupp->no_cons_clientpq, (el_func_t)vntsd_notify_client_cons_del); to.tv_sec = VNTSD_CV_WAIT_DELTIME; to.tv_nsec = 0; (void) cond_reltimedwait(&groupp->cvp, &groupp->lock, &to); } /* waiting for listen thread to exit */ while (groupp->status & VNTSD_GROUP_SIG_WAIT) { /* signal listen thread to exit */ (void) thr_kill(groupp->listen_tid, SIGUSR1); to.tv_sec = VNTSD_CV_WAIT_DELTIME; to.tv_nsec = 0; /* wait listen thread to exit */ (void) cond_reltimedwait(&groupp->cvp, &groupp->lock, &to); } (void) mutex_unlock(&groupp->lock); (void) thr_join(groupp->listen_tid, NULL, NULL); /* free group */ free_group(groupp); } /* allocate and initialize console structure */ static vntsd_cons_t * alloc_cons(vntsd_group_t *groupp, vcc_console_t *consolep) { vntsd_cons_t *consp; int rv; /* allocate console */ consp = (vntsd_cons_t *)malloc(sizeof (vntsd_cons_t)); if (consp == NULL) { vntsd_log(VNTSD_ERR_NO_MEM, "alloc_cons"); return (NULL); } /* intialize console */ bzero(consp, sizeof (vntsd_cons_t)); (void) mutex_init(&consp->lock, USYNC_THREAD|LOCK_ERRORCHECK, NULL); (void) cond_init(&consp->cvp, USYNC_THREAD, NULL); consp->cons_no = consolep->cons_no; (void) strlcpy(consp->domain_name, consolep->domain_name, MAXPATHLEN); (void) strlcpy(consp->dev_name, consolep->dev_name, MAXPATHLEN); consp->wr_tid = (thread_t)-1; consp->vcc_fd = -1; /* join the group */ (void) mutex_lock(&groupp->lock); if ((rv = vntsd_que_append(&groupp->conspq, consp)) != VNTSD_SUCCESS) { (void) mutex_unlock(&groupp->lock); vntsd_log(rv, "alloc_cons"); free_cons(consp); return (NULL); } groupp->num_cons++; consp->group = groupp; (void) mutex_unlock(&groupp->lock); D1(stderr, "t@%d alloc_cons@%d %s %s\n", thr_self(), consp->cons_no, consp->domain_name, consp->dev_name); return (consp); } /* compare tcp with group->tcp */ static boolean_t grp_by_tcp(vntsd_group_t *groupp, uint64_t *tcp_port) { assert(groupp); assert(tcp_port); return (groupp->tcp_port == *tcp_port); } /* allocate and initialize group */ static vntsd_group_t * alloc_group(vntsd_t *vntsdp, char *group_name, uint64_t tcp_port) { vntsd_group_t *groupp; /* allocate group */ groupp = (vntsd_group_t *)malloc(sizeof (vntsd_group_t)); if (groupp == NULL) { vntsd_log(VNTSD_ERR_NO_MEM, "alloc_group"); return (NULL); } /* initialize group */ bzero(groupp, sizeof (vntsd_group_t)); (void) mutex_init(&groupp->lock, USYNC_THREAD|LOCK_ERRORCHECK, NULL); (void) cond_init(&groupp->cvp, USYNC_THREAD, NULL); if (group_name != NULL) { (void) memcpy(groupp->group_name, group_name, MAXPATHLEN); } groupp->tcp_port = tcp_port; groupp->listen_tid = (thread_t)-1; groupp->sockfd = -1; groupp->vntsd = vntsdp; D1(stderr, "t@%d alloc_group@%lld:%s\n", thr_self(), groupp->tcp_port, groupp->group_name); return (groupp); } /* mark a deleted console */ boolean_t vntsd_mark_deleted_cons(vntsd_cons_t *consp) { (void) mutex_lock(&consp->lock); consp->status |= VNTSD_CONS_DELETED; (void) mutex_unlock(&consp->lock); return (B_FALSE); } /* * Initialize a console, if console is associated with with a * new group, intialize the group. */ static int alloc_cons_with_group(vntsd_t *vntsdp, vcc_console_t *consp, vntsd_group_t **new_groupp) { vntsd_group_t *groupp = NULL; int rv; *new_groupp = NULL; /* match group by tcp port */ (void) mutex_lock(&vntsdp->lock); groupp = vntsd_que_find(vntsdp->grouppq, (compare_func_t)grp_by_tcp, (void *)&(consp->tcp_port)); if (groupp != NULL) (void) mutex_lock(&groupp->lock); (void) mutex_unlock(&vntsdp->lock); if (groupp != NULL) { /* * group with same tcp port found. * if there is no console in the group, the * group should be removed and the tcp port can * be used for tne new group. * This is possible, when there is tight loop of * creating/deleting domains. When a vcc port is * removed, a read thread will have an I/O error because * vcc has closed the port. The read thread then marks * the console is removed and notify main thread to * remove the console. * Meanwhile, the same port and its group (with same * tcp port and group name) is created. Vcc notify * vntsd that new console is added. * Main thread now have two events. If main thread polls * out vcc notification first, it will find that there is * a group has no console. */ if (vntsd_chk_group_total_cons(groupp) == 0) { /* all consoles in the group have been removed */ (void) vntsd_que_walk(groupp->conspq, (el_func_t)vntsd_mark_deleted_cons); groupp->status |= VNTSD_GROUP_CLEAN_CONS; (void) mutex_unlock(&groupp->lock); groupp = NULL; } else if (strcmp(groupp->group_name, consp->group_name)) { /* conflict group name */ vntsd_log(VNTSD_ERR_VCC_GRP_NAME, "group name is different from existing group"); (void) mutex_unlock(&groupp->lock); return (VNTSD_ERR_VCC_CTRL_DATA); } else { /* group already existed */ (void) mutex_unlock(&groupp->lock); } } if (groupp == NULL) { /* new group */ groupp = alloc_group(vntsdp, consp->group_name, consp->tcp_port); if (groupp == NULL) { return (VNTSD_ERR_NO_MEM); } assert(groupp->conspq == NULL); /* queue group to vntsdp */ (void) mutex_lock(&vntsdp->lock); rv = vntsd_que_append(&vntsdp->grouppq, groupp); (void) mutex_unlock(&vntsdp->lock); if (rv != VNTSD_SUCCESS) { return (rv); } *new_groupp = groupp; } /* intialize console */ if (alloc_cons(groupp, consp) == NULL) { /* no memory */ if (new_groupp != NULL) { /* clean up new group */ free_group(groupp); } return (VNTSD_ERR_NO_MEM); } return (VNTSD_SUCCESS); } /* create listen thread */ static boolean_t create_listen_thread(vntsd_group_t *groupp) { char err_msg[VNTSD_LINE_LEN]; int rv; assert(groupp); (void) mutex_lock(&groupp->lock); assert(groupp->num_cons); D1(stderr, "t@%d create_listen:%lld\n", thr_self(), groupp->tcp_port); if ((rv = thr_create(NULL, 0, (thr_func_t)vntsd_listen_thread, (void *)groupp, THR_DETACHED, &groupp->listen_tid)) != 0) { (void) (void) snprintf(err_msg, sizeof (err_msg), "Can not create listen thread for" "group %s tcp %llx\n", groupp->group_name, groupp->tcp_port); vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, err_msg); /* clean up group queue */ vntsd_free_que(&groupp->conspq, (clean_func_t)free_cons); groupp->listen_tid = (thread_t)-1; } (void) mutex_unlock(&groupp->lock); return (rv != 0); } /* find deleted console by console no */ static boolean_t deleted_cons_by_consno(vntsd_cons_t *consp, int *cons_no) { vntsd_client_t *clientp; assert(consp); if (consp->cons_no != *cons_no) return (B_FALSE); /* has console marked as deleted? */ if ((consp->status & VNTSD_CONS_DELETED) == 0) return (B_TRUE); /* notify clients of console ? */ clientp = (vntsd_client_t *)consp->clientpq->handle; if (clientp == NULL) /* therre is no client for this console */ return (B_TRUE); if (clientp->status & VNTSD_CLIENT_CONS_DELETED) /* clients of console have notified */ return (B_FALSE); return (B_TRUE); } /* find group structure from console no */ static boolean_t find_cons_group_by_cons_no(vntsd_group_t *groupp, uint_t *cons_no) { vntsd_cons_t *consp; consp = vntsd_que_find(groupp->conspq, (compare_func_t)deleted_cons_by_consno, cons_no); return (consp != NULL); } /* delete a console if the console exists in the vntsd */ static void delete_cons_before_add(vntsd_t *vntsdp, uint_t cons_no) { vntsd_group_t *groupp; vntsd_cons_t *consp; /* group exists? */ (void) mutex_lock(&vntsdp->lock); groupp = vntsd_que_find(vntsdp->grouppq, (compare_func_t)find_cons_group_by_cons_no, &cons_no); (void) mutex_unlock(&vntsdp->lock); if (groupp == NULL) { /* no such group */ return; } /* group exists, if console exists? */ (void) mutex_lock(&groupp->lock); consp = vntsd_que_find(groupp->conspq, (compare_func_t)deleted_cons_by_consno, &cons_no); if (consp == NULL) { /* no such console */ (void) mutex_unlock(&groupp->lock); return; } /* console exists - delete console */ (void) mutex_lock(&consp->lock); consp->status |= VNTSD_CONS_DELETED; groupp->status |= VNTSD_GROUP_CLEAN_CONS; (void) mutex_unlock(&consp->lock); (void) mutex_unlock(&groupp->lock); vntsd_delete_cons(vntsdp); } /* add a console */ static void do_add_cons(vntsd_t *vntsdp, int cons_no) { vcc_console_t console; vntsd_group_t *groupp; int rv; char err_msg[VNTSD_LINE_LEN]; (void) snprintf(err_msg, sizeof (err_msg), "do_add_cons():Can not add console=%d", cons_no); /* get console configuration from vcc */ if ((rv = vntsd_vcc_ioctl(VCC_CONS_INFO, cons_no, (void *)&console)) != VNTSD_SUCCESS) { vntsd_log(rv, err_msg); return; } /* clean up the console if console was deleted and added again */ delete_cons_before_add(vntsdp, console.cons_no); /* initialize console */ if ((rv = alloc_cons_with_group(vntsdp, &console, &groupp)) != VNTSD_SUCCESS) { /* no memory to add this new console */ vntsd_log(rv, err_msg); return; } if (groupp != NULL) { /* new group */ /* create listen thread for this console */ if (create_listen_thread(groupp)) { vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, err_msg); free_group(groupp); } } } /* daemon wake up */ void vntsd_daemon_wakeup(vntsd_t *vntsdp) { vcc_response_t inq_data; /* reason to wake up */ if (vntsd_vcc_ioctl(VCC_INQUIRY, 0, (void *)&inq_data) != VNTSD_SUCCESS) { vntsd_log(VNTSD_ERR_VCC_IOCTL, "vntsd_daemon_wakeup()"); return; } D1(stderr, "t@%d vntsd_daemon_wakup:msg %d port %x\n", thr_self(), inq_data.reason, inq_data.cons_no); switch (inq_data.reason) { case VCC_CONS_ADDED: do_add_cons(vntsdp, inq_data.cons_no); break; case VCC_CONS_MISS_ADDED: /* an added port was deleted before vntsd can process it */ return; default: DERR(stderr, "t@%d daemon_wakeup:ioctl_unknown %d\n", thr_self(), inq_data.reason); vntsd_log(VNTSD_ERR_UNKNOWN_CMD, "from vcc\n"); break; } } /* initial console configuration */ void vntsd_get_config(vntsd_t *vntsdp) { int i; int num_cons; vcc_console_t *consp; vntsd_group_t *groupp; /* num of consoles */ num_cons = 0; if (vntsd_vcc_ioctl(VCC_NUM_CONSOLE, 0, (void *)&num_cons) != VNTSD_SUCCESS) { vntsd_log(VNTSD_ERR_VCC_IOCTL, "VCC_NUM_CONSOLE failed\n"); return; } D3(stderr, "get_config:num_cons=%d", num_cons); if (num_cons == 0) { return; } /* allocate memory for all consoles */ consp = malloc(num_cons*sizeof (vcc_console_t)); if (consp == NULL) { vntsd_log(VNTSD_ERR_NO_MEM, "for console table."); return; } /* get console table */ if (vntsd_vcc_ioctl(VCC_CONS_TBL, 0, (void *)consp) != VNTSD_SUCCESS) { vntsd_log(VNTSD_ERR_VCC_IOCTL, " VCC_CONS_TBL " "for console table\n"); return; } /* intialize groups and consoles */ for (i = 0; i < num_cons; i++) { if (alloc_cons_with_group(vntsdp, &consp[i], &groupp) != VNTSD_SUCCESS) { vntsd_log(VNTSD_ERR_ADD_CONS_FAILED, "get_config"); } } /* create listen thread for each group */ (void) mutex_lock(&vntsdp->lock); for (; ; ) { groupp = vntsd_que_walk(vntsdp->grouppq, (el_func_t)create_listen_thread); if (groupp == NULL) { break; } vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, "get config()"); } (void) mutex_unlock(&vntsdp->lock); }