diff --git a/sys/dev/mlx/mlx.c b/sys/dev/mlx/mlx.c index bafd0902e09a..f5b023eafc9c 100644 --- a/sys/dev/mlx/mlx.c +++ b/sys/dev/mlx/mlx.c @@ -1,3076 +1,3076 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1999 Michael Smith * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ /* * Driver for the Mylex DAC960 family of RAID controllers. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct cdevsw mlx_cdevsw = { .d_version = D_VERSION, .d_open = mlx_open, .d_close = mlx_close, .d_ioctl = mlx_ioctl, .d_name = "mlx", }; devclass_t mlx_devclass; /* * Per-interface accessor methods */ static int mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); static int mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); static void mlx_v3_intaction(struct mlx_softc *sc, int action); static int mlx_v3_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first); static int mlx_v4_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); static int mlx_v4_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); static void mlx_v4_intaction(struct mlx_softc *sc, int action); static int mlx_v4_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first); static int mlx_v5_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); static int mlx_v5_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); static void mlx_v5_intaction(struct mlx_softc *sc, int action); static int mlx_v5_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first); /* * Status monitoring */ static void mlx_periodic(void *data); static void mlx_periodic_enquiry(struct mlx_command *mc); static void mlx_periodic_eventlog_poll(struct mlx_softc *sc); static void mlx_periodic_eventlog_respond(struct mlx_command *mc); static void mlx_periodic_rebuild(struct mlx_command *mc); /* * Channel Pause */ static void mlx_pause_action(struct mlx_softc *sc); static void mlx_pause_done(struct mlx_command *mc); /* * Command submission. */ static void *mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, void (*complete)(struct mlx_command *mc)); static int mlx_flush(struct mlx_softc *sc); static int mlx_check(struct mlx_softc *sc, int drive); static int mlx_rebuild(struct mlx_softc *sc, int channel, int target); static int mlx_wait_command(struct mlx_command *mc); static int mlx_poll_command(struct mlx_command *mc); void mlx_startio_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error); static void mlx_startio(struct mlx_softc *sc); static void mlx_completeio(struct mlx_command *mc); static int mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu); void mlx_user_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error); /* * Command buffer allocation. */ static struct mlx_command *mlx_alloccmd(struct mlx_softc *sc); static void mlx_releasecmd(struct mlx_command *mc); static void mlx_freecmd(struct mlx_command *mc); /* * Command management. */ static int mlx_getslot(struct mlx_command *mc); static void mlx_setup_dmamap(struct mlx_command *mc, bus_dma_segment_t *segs, int nsegments, int error); static void mlx_unmapcmd(struct mlx_command *mc); static int mlx_shutdown_locked(struct mlx_softc *sc); static int mlx_start(struct mlx_command *mc); static int mlx_done(struct mlx_softc *sc, int startio); static void mlx_complete(struct mlx_softc *sc); /* * Debugging. */ static char *mlx_diagnose_command(struct mlx_command *mc); static void mlx_describe_controller(struct mlx_softc *sc); static int mlx_fw_message(struct mlx_softc *sc, int status, int param1, int param2); /* * Utility functions. */ static struct mlx_sysdrive *mlx_findunit(struct mlx_softc *sc, int unit); /******************************************************************************** ******************************************************************************** Public Interfaces ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ void mlx_free(struct mlx_softc *sc) { struct mlx_command *mc; debug_called(1); /* destroy control device */ if (sc->mlx_dev_t != NULL) destroy_dev(sc->mlx_dev_t); if (sc->mlx_intr) bus_teardown_intr(sc->mlx_dev, sc->mlx_irq, sc->mlx_intr); /* cancel status timeout */ MLX_IO_LOCK(sc); callout_stop(&sc->mlx_timeout); /* throw away any command buffers */ while ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) { TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link); mlx_freecmd(mc); } MLX_IO_UNLOCK(sc); callout_drain(&sc->mlx_timeout); /* destroy data-transfer DMA tag */ if (sc->mlx_buffer_dmat) bus_dma_tag_destroy(sc->mlx_buffer_dmat); /* free and destroy DMA memory and tag for s/g lists */ if (sc->mlx_sgbusaddr) bus_dmamap_unload(sc->mlx_sg_dmat, sc->mlx_sg_dmamap); if (sc->mlx_sgtable) bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap); if (sc->mlx_sg_dmat) bus_dma_tag_destroy(sc->mlx_sg_dmat); /* disconnect the interrupt handler */ if (sc->mlx_irq != NULL) bus_release_resource(sc->mlx_dev, SYS_RES_IRQ, 0, sc->mlx_irq); /* destroy the parent DMA tag */ if (sc->mlx_parent_dmat) bus_dma_tag_destroy(sc->mlx_parent_dmat); /* release the register window mapping */ if (sc->mlx_mem != NULL) bus_release_resource(sc->mlx_dev, sc->mlx_mem_type, sc->mlx_mem_rid, sc->mlx_mem); /* free controller enquiry data */ if (sc->mlx_enq2 != NULL) free(sc->mlx_enq2, M_DEVBUF); sx_destroy(&sc->mlx_config_lock); mtx_destroy(&sc->mlx_io_lock); } /******************************************************************************** * Map the scatter/gather table into bus space */ static void mlx_dma_map_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mlx_softc *sc = (struct mlx_softc *)arg; debug_called(1); /* save base of s/g table's address in bus space */ sc->mlx_sgbusaddr = segs->ds_addr; } static int mlx_sglist_map(struct mlx_softc *sc) { size_t segsize; int error, ncmd; debug_called(1); /* destroy any existing mappings */ if (sc->mlx_sgbusaddr) bus_dmamap_unload(sc->mlx_sg_dmat, sc->mlx_sg_dmamap); if (sc->mlx_sgtable) bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap); if (sc->mlx_sg_dmat) bus_dma_tag_destroy(sc->mlx_sg_dmat); sc->mlx_sgbusaddr = 0; sc->mlx_sgtable = NULL; sc->mlx_sg_dmat = NULL; /* * Create a single tag describing a region large enough to hold all of * the s/g lists we will need. If we're called early on, we don't know how * many commands we're going to be asked to support, so only allocate enough * for a couple. */ if (sc->mlx_enq2 == NULL) { ncmd = 2; } else { ncmd = sc->mlx_enq2->me_max_commands; } segsize = sizeof(struct mlx_sgentry) * MLX_NSEG * ncmd; error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */ 1, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ segsize, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mlx_sg_dmat); if (error != 0) { device_printf(sc->mlx_dev, "can't allocate scatter/gather DMA tag\n"); return(ENOMEM); } /* * Allocate enough s/g maps for all commands and permanently map them into * controller-visible space. * * XXX this assumes we can get enough space for all the s/g maps in one * contiguous slab. We may need to switch to a more complex arrangement * where we allocate in smaller chunks and keep a lookup table from slot * to bus address. */ error = bus_dmamem_alloc(sc->mlx_sg_dmat, (void **)&sc->mlx_sgtable, BUS_DMA_NOWAIT, &sc->mlx_sg_dmamap); if (error) { device_printf(sc->mlx_dev, "can't allocate s/g table\n"); return(ENOMEM); } (void)bus_dmamap_load(sc->mlx_sg_dmat, sc->mlx_sg_dmamap, sc->mlx_sgtable, segsize, mlx_dma_map_sg, sc, 0); return(0); } /******************************************************************************** * Initialise the controller and softc */ int mlx_attach(struct mlx_softc *sc) { struct mlx_enquiry_old *meo; int rid, error, fwminor, hscode, hserror, hsparam1, hsparam2, hsmsg; debug_called(1); /* * Initialise per-controller queues. */ TAILQ_INIT(&sc->mlx_work); TAILQ_INIT(&sc->mlx_freecmds); bioq_init(&sc->mlx_bioq); /* * Select accessor methods based on controller interface type. */ switch(sc->mlx_iftype) { case MLX_IFTYPE_2: case MLX_IFTYPE_3: sc->mlx_tryqueue = mlx_v3_tryqueue; sc->mlx_findcomplete = mlx_v3_findcomplete; sc->mlx_intaction = mlx_v3_intaction; sc->mlx_fw_handshake = mlx_v3_fw_handshake; break; case MLX_IFTYPE_4: sc->mlx_tryqueue = mlx_v4_tryqueue; sc->mlx_findcomplete = mlx_v4_findcomplete; sc->mlx_intaction = mlx_v4_intaction; sc->mlx_fw_handshake = mlx_v4_fw_handshake; break; case MLX_IFTYPE_5: sc->mlx_tryqueue = mlx_v5_tryqueue; sc->mlx_findcomplete = mlx_v5_findcomplete; sc->mlx_intaction = mlx_v5_intaction; sc->mlx_fw_handshake = mlx_v5_fw_handshake; break; default: return(ENXIO); /* should never happen */ } /* disable interrupts before we start talking to the controller */ MLX_IO_LOCK(sc); sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); MLX_IO_UNLOCK(sc); /* * Wait for the controller to come ready, handshake with the firmware if required. * This is typically only necessary on platforms where the controller BIOS does not * run. */ hsmsg = 0; DELAY(1000); while ((hscode = sc->mlx_fw_handshake(sc, &hserror, &hsparam1, &hsparam2, hsmsg == 0)) != 0) { /* report first time around... */ if (hsmsg == 0) { device_printf(sc->mlx_dev, "controller initialisation in progress...\n"); hsmsg = 1; } /* did we get a real message? */ if (hscode == 2) { hscode = mlx_fw_message(sc, hserror, hsparam1, hsparam2); /* fatal initialisation error? */ if (hscode != 0) { return(ENXIO); } } } if (hsmsg == 1) device_printf(sc->mlx_dev, "initialisation complete.\n"); /* * Allocate and connect our interrupt. */ rid = 0; sc->mlx_irq = bus_alloc_resource_any(sc->mlx_dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->mlx_irq == NULL) { device_printf(sc->mlx_dev, "can't allocate interrupt\n"); return(ENXIO); } error = bus_setup_intr(sc->mlx_dev, sc->mlx_irq, INTR_TYPE_BIO | INTR_ENTROPY | INTR_MPSAFE, NULL, mlx_intr, sc, &sc->mlx_intr); if (error) { device_printf(sc->mlx_dev, "can't set up interrupt\n"); return(ENXIO); } /* * Create DMA tag for mapping buffers into controller-addressable space. */ error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */ 1, 0, /* align, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MLX_MAXPHYS, /* maxsize */ MLX_NSEG, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->mlx_io_lock, /* lockarg */ &sc->mlx_buffer_dmat); if (error != 0) { device_printf(sc->mlx_dev, "can't allocate buffer DMA tag\n"); return(ENOMEM); } /* * Create some initial scatter/gather mappings so we can run the probe * commands. */ error = mlx_sglist_map(sc); if (error != 0) { device_printf(sc->mlx_dev, "can't make initial s/g list mapping\n"); return(error); } /* * We don't (yet) know where the event log is up to. */ sc->mlx_currevent = -1; /* * Obtain controller feature information */ MLX_IO_LOCK(sc); if ((sc->mlx_enq2 = mlx_enquire(sc, MLX_CMD_ENQUIRY2, sizeof(struct mlx_enquiry2), NULL)) == NULL) { MLX_IO_UNLOCK(sc); device_printf(sc->mlx_dev, "ENQUIRY2 failed\n"); return(ENXIO); } /* * Do quirk/feature related things. */ fwminor = (sc->mlx_enq2->me_firmware_id >> 8) & 0xff; switch(sc->mlx_iftype) { case MLX_IFTYPE_2: /* These controllers don't report the firmware version in the ENQUIRY2 response */ if ((meo = mlx_enquire(sc, MLX_CMD_ENQUIRY_OLD, sizeof(struct mlx_enquiry_old), NULL)) == NULL) { MLX_IO_UNLOCK(sc); device_printf(sc->mlx_dev, "ENQUIRY_OLD failed\n"); return(ENXIO); } sc->mlx_enq2->me_firmware_id = ('0' << 24) | (0 << 16) | (meo->me_fwminor << 8) | meo->me_fwmajor; /* XXX require 2.42 or better (PCI) */ if (meo->me_fwminor < 42) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 2.42 or later\n"); } free(meo, M_DEVBUF); break; case MLX_IFTYPE_3: /* XXX certify 3.52? */ if (fwminor < 51) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 3.51 or later\n"); } break; case MLX_IFTYPE_4: /* XXX certify firmware versions? */ if (fwminor < 6) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 4.06 or later\n"); } break; case MLX_IFTYPE_5: if (fwminor < 7) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 5.07 or later\n"); } break; default: MLX_IO_UNLOCK(sc); return(ENXIO); /* should never happen */ } MLX_IO_UNLOCK(sc); /* * Create the final scatter/gather mappings now that we have characterised the controller. */ error = mlx_sglist_map(sc); if (error != 0) { device_printf(sc->mlx_dev, "can't make final s/g list mapping\n"); return(error); } /* * No user-requested background operation is in progress. */ sc->mlx_background = 0; sc->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE; /* * Create the control device. */ sc->mlx_dev_t = make_dev(&mlx_cdevsw, 0, UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "mlx%d", device_get_unit(sc->mlx_dev)); sc->mlx_dev_t->si_drv1 = sc; /* * Start the timeout routine. */ callout_reset(&sc->mlx_timeout, hz, mlx_periodic, sc); /* print a little information about the controller */ mlx_describe_controller(sc); return(0); } /******************************************************************************** * Locate disk resources and attach children to them. */ void mlx_startup(struct mlx_softc *sc) { struct mlx_enq_sys_drive *mes; struct mlx_sysdrive *dr; int i, error; debug_called(1); /* * Scan all the system drives and attach children for those that * don't currently have them. */ MLX_IO_LOCK(sc); mes = mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(*mes) * MLX_MAXDRIVES, NULL); MLX_IO_UNLOCK(sc); if (mes == NULL) { device_printf(sc->mlx_dev, "error fetching drive status\n"); return; } /* iterate over drives returned */ MLX_CONFIG_LOCK(sc); for (i = 0, dr = &sc->mlx_sysdrive[0]; (i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff); i++, dr++) { /* are we already attached to this drive? */ if (dr->ms_disk == 0) { /* pick up drive information */ dr->ms_size = mes[i].sd_size; dr->ms_raidlevel = mes[i].sd_raidlevel & 0xf; dr->ms_state = mes[i].sd_state; /* generate geometry information */ if (sc->mlx_geom == MLX_GEOM_128_32) { dr->ms_heads = 128; dr->ms_sectors = 32; dr->ms_cylinders = dr->ms_size / (128 * 32); } else { /* MLX_GEOM_255/63 */ dr->ms_heads = 255; dr->ms_sectors = 63; dr->ms_cylinders = dr->ms_size / (255 * 63); } dr->ms_disk = device_add_child(sc->mlx_dev, /*"mlxd"*/NULL, -1); if (dr->ms_disk == 0) device_printf(sc->mlx_dev, "device_add_child failed\n"); device_set_ivars(dr->ms_disk, dr); } } free(mes, M_DEVBUF); if ((error = bus_generic_attach(sc->mlx_dev)) != 0) device_printf(sc->mlx_dev, "bus_generic_attach returned %d", error); /* mark controller back up */ MLX_IO_LOCK(sc); sc->mlx_state &= ~MLX_STATE_SHUTDOWN; /* enable interrupts */ sc->mlx_intaction(sc, MLX_INTACTION_ENABLE); MLX_IO_UNLOCK(sc); MLX_CONFIG_UNLOCK(sc); } /******************************************************************************** * Disconnect from the controller completely, in preparation for unload. */ int mlx_detach(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); struct mlxd_softc *mlxd; int i, error; debug_called(1); error = EBUSY; MLX_CONFIG_LOCK(sc); if (sc->mlx_state & MLX_STATE_OPEN) goto out; for (i = 0; i < MLX_MAXDRIVES; i++) { if (sc->mlx_sysdrive[i].ms_disk != 0) { mlxd = device_get_softc(sc->mlx_sysdrive[i].ms_disk); if (mlxd->mlxd_flags & MLXD_OPEN) { /* drive is mounted, abort detach */ device_printf(sc->mlx_sysdrive[i].ms_disk, "still open, can't detach\n"); goto out; } } } if ((error = mlx_shutdown(dev))) goto out; MLX_CONFIG_UNLOCK(sc); mlx_free(sc); return (0); out: MLX_CONFIG_UNLOCK(sc); return(error); } /******************************************************************************** * Bring the controller down to a dormant state and detach all child devices. * * This function is called before detach, system shutdown, or before performing * an operation which may add or delete system disks. (Call mlx_startup to * resume normal operation.) * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ int mlx_shutdown(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); int error; MLX_CONFIG_LOCK(sc); error = mlx_shutdown_locked(sc); MLX_CONFIG_UNLOCK(sc); return (error); } static int mlx_shutdown_locked(struct mlx_softc *sc) { int i, error; debug_called(1); MLX_CONFIG_ASSERT_LOCKED(sc); MLX_IO_LOCK(sc); sc->mlx_state |= MLX_STATE_SHUTDOWN; sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); /* flush controller */ device_printf(sc->mlx_dev, "flushing cache..."); if (mlx_flush(sc)) { printf("failed\n"); } else { printf("done\n"); } MLX_IO_UNLOCK(sc); /* delete all our child devices */ for (i = 0; i < MLX_MAXDRIVES; i++) { if (sc->mlx_sysdrive[i].ms_disk != 0) { if ((error = device_delete_child(sc->mlx_dev, sc->mlx_sysdrive[i].ms_disk)) != 0) return (error); sc->mlx_sysdrive[i].ms_disk = 0; } } return (0); } /******************************************************************************** * Bring the controller to a quiescent state, ready for system suspend. */ int mlx_suspend(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); debug_called(1); MLX_IO_LOCK(sc); sc->mlx_state |= MLX_STATE_SUSPEND; /* flush controller */ device_printf(sc->mlx_dev, "flushing cache..."); printf("%s\n", mlx_flush(sc) ? "failed" : "done"); sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); MLX_IO_UNLOCK(sc); return(0); } /******************************************************************************** * Bring the controller back to a state ready for operation. */ int mlx_resume(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); debug_called(1); MLX_IO_LOCK(sc); sc->mlx_state &= ~MLX_STATE_SUSPEND; sc->mlx_intaction(sc, MLX_INTACTION_ENABLE); MLX_IO_UNLOCK(sc); return(0); } /******************************************************************************* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ void mlx_intr(void *arg) { struct mlx_softc *sc = (struct mlx_softc *)arg; debug_called(1); /* collect finished commands, queue anything waiting */ MLX_IO_LOCK(sc); mlx_done(sc, 1); MLX_IO_UNLOCK(sc); }; /******************************************************************************* * Receive a buf structure from a child device and queue it on a particular * disk resource, then poke the disk resource to start as much work as it can. */ int mlx_submit_buf(struct mlx_softc *sc, struct bio *bp) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); bioq_insert_tail(&sc->mlx_bioq, bp); sc->mlx_waitbufs++; mlx_startio(sc); return(0); } /******************************************************************************** * Accept an open operation on the control device. */ int mlx_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mlx_softc *sc = dev->si_drv1; MLX_CONFIG_LOCK(sc); MLX_IO_LOCK(sc); sc->mlx_state |= MLX_STATE_OPEN; MLX_IO_UNLOCK(sc); MLX_CONFIG_UNLOCK(sc); return(0); } /******************************************************************************** * Accept the last close on the control device. */ int mlx_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mlx_softc *sc = dev->si_drv1; MLX_CONFIG_LOCK(sc); MLX_IO_LOCK(sc); sc->mlx_state &= ~MLX_STATE_OPEN; MLX_IO_UNLOCK(sc); MLX_CONFIG_UNLOCK(sc); return (0); } /******************************************************************************** * Handle controller-specific control operations. */ int mlx_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { struct mlx_softc *sc = dev->si_drv1; struct mlx_rebuild_request *rb = (struct mlx_rebuild_request *)addr; struct mlx_rebuild_status *rs = (struct mlx_rebuild_status *)addr; int *arg = (int *)addr; struct mlx_pause *mp; struct mlx_sysdrive *dr; struct mlxd_softc *mlxd; int i, error; switch(cmd) { /* * Enumerate connected system drives; returns the first system drive's * unit number if *arg is -1, or the next unit after *arg if it's * a valid unit on this controller. */ case MLX_NEXT_CHILD: /* search system drives */ MLX_CONFIG_LOCK(sc); for (i = 0; i < MLX_MAXDRIVES; i++) { /* is this one attached? */ if (sc->mlx_sysdrive[i].ms_disk != 0) { /* looking for the next one we come across? */ if (*arg == -1) { *arg = device_get_unit(sc->mlx_sysdrive[i].ms_disk); MLX_CONFIG_UNLOCK(sc); return(0); } /* we want the one after this one */ if (*arg == device_get_unit(sc->mlx_sysdrive[i].ms_disk)) *arg = -1; } } MLX_CONFIG_UNLOCK(sc); return(ENOENT); /* * Scan the controller to see whether new drives have appeared. */ case MLX_RESCAN_DRIVES: mtx_lock(&Giant); mlx_startup(sc); mtx_unlock(&Giant); return(0); /* * Disconnect from the specified drive; it may be about to go * away. */ case MLX_DETACH_DRIVE: /* detach one drive */ MLX_CONFIG_LOCK(sc); if (((dr = mlx_findunit(sc, *arg)) == NULL) || ((mlxd = device_get_softc(dr->ms_disk)) == NULL)) { MLX_CONFIG_UNLOCK(sc); return(ENOENT); } device_printf(dr->ms_disk, "detaching..."); error = 0; if (mlxd->mlxd_flags & MLXD_OPEN) { error = EBUSY; goto detach_out; } /* flush controller */ MLX_IO_LOCK(sc); if (mlx_flush(sc)) { MLX_IO_UNLOCK(sc); error = EBUSY; goto detach_out; } MLX_IO_UNLOCK(sc); /* nuke drive */ if ((error = device_delete_child(sc->mlx_dev, dr->ms_disk)) != 0) goto detach_out; dr->ms_disk = 0; detach_out: MLX_CONFIG_UNLOCK(sc); if (error) { printf("failed\n"); } else { printf("done\n"); } return(error); /* * Pause one or more SCSI channels for a period of time, to assist * in the process of hot-swapping devices. * * Note that at least the 3.51 firmware on the DAC960PL doesn't seem * to do this right. */ case MLX_PAUSE_CHANNEL: /* schedule a channel pause */ /* Does this command work on this firmware? */ if (!(sc->mlx_feature & MLX_FEAT_PAUSEWORKS)) return(EOPNOTSUPP); /* check time values */ mp = (struct mlx_pause *)addr; if ((mp->mp_when < 0) || (mp->mp_when > 3600)) return(EINVAL); if ((mp->mp_howlong < 1) || (mp->mp_howlong > (0xf * 30))) return(EINVAL); MLX_IO_LOCK(sc); if ((mp->mp_which == MLX_PAUSE_CANCEL) && (sc->mlx_pause.mp_when != 0)) { /* cancel a pending pause operation */ sc->mlx_pause.mp_which = 0; } else { /* fix for legal channels */ mp->mp_which &= ((1 << sc->mlx_enq2->me_actual_channels) -1); /* check for a pause currently running */ if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when == 0)) { MLX_IO_UNLOCK(sc); return(EBUSY); } /* looks ok, go with it */ sc->mlx_pause.mp_which = mp->mp_which; sc->mlx_pause.mp_when = time_second + mp->mp_when; sc->mlx_pause.mp_howlong = sc->mlx_pause.mp_when + mp->mp_howlong; } MLX_IO_UNLOCK(sc); return(0); /* * Accept a command passthrough-style. */ case MLX_COMMAND: return(mlx_user_command(sc, (struct mlx_usercommand *)addr)); /* * Start a rebuild on a given SCSI disk */ case MLX_REBUILDASYNC: MLX_IO_LOCK(sc); if (sc->mlx_background != 0) { MLX_IO_UNLOCK(sc); rb->rr_status = 0x0106; return(EBUSY); } rb->rr_status = mlx_rebuild(sc, rb->rr_channel, rb->rr_target); switch (rb->rr_status) { case 0: error = 0; break; case 0x10000: error = ENOMEM; /* couldn't set up the command */ break; case 0x0002: error = EBUSY; break; case 0x0104: error = EIO; break; case 0x0105: error = ERANGE; break; case 0x0106: error = EBUSY; break; default: error = EINVAL; break; } if (error == 0) sc->mlx_background = MLX_BACKGROUND_REBUILD; MLX_IO_UNLOCK(sc); return(error); /* * Get the status of the current rebuild or consistency check. */ case MLX_REBUILDSTAT: MLX_IO_LOCK(sc); *rs = sc->mlx_rebuildstat; MLX_IO_UNLOCK(sc); return(0); /* * Return the per-controller system drive number matching the * disk device number in (arg), if it happens to belong to us. */ case MLX_GET_SYSDRIVE: error = ENOENT; MLX_CONFIG_LOCK(sc); mtx_lock(&Giant); mlxd = (struct mlxd_softc *)devclass_get_softc(mlxd_devclass, *arg); mtx_unlock(&Giant); if ((mlxd != NULL) && (mlxd->mlxd_drive >= sc->mlx_sysdrive) && (mlxd->mlxd_drive < (sc->mlx_sysdrive + MLX_MAXDRIVES))) { error = 0; *arg = mlxd->mlxd_drive - sc->mlx_sysdrive; } MLX_CONFIG_UNLOCK(sc); return(error); default: return(ENOTTY); } } /******************************************************************************** * Handle operations requested by a System Drive connected to this controller. */ int mlx_submit_ioctl(struct mlx_softc *sc, struct mlx_sysdrive *drive, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { int *arg = (int *)addr; int error, result; switch(cmd) { /* * Return the current status of this drive. */ case MLXD_STATUS: MLX_IO_LOCK(sc); *arg = drive->ms_state; MLX_IO_UNLOCK(sc); return(0); /* * Start a background consistency check on this drive. */ case MLXD_CHECKASYNC: /* start a background consistency check */ MLX_IO_LOCK(sc); if (sc->mlx_background != 0) { MLX_IO_UNLOCK(sc); *arg = 0x0106; return(EBUSY); } result = mlx_check(sc, drive - &sc->mlx_sysdrive[0]); switch (result) { case 0: error = 0; break; case 0x10000: error = ENOMEM; /* couldn't set up the command */ break; case 0x0002: error = EIO; break; case 0x0105: error = ERANGE; break; case 0x0106: error = EBUSY; break; default: error = EINVAL; break; } if (error == 0) sc->mlx_background = MLX_BACKGROUND_CHECK; MLX_IO_UNLOCK(sc); *arg = result; return(error); } return(ENOIOCTL); } /******************************************************************************** ******************************************************************************** Status Monitoring ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Fire off commands to periodically check the status of connected drives. */ static void mlx_periodic(void *data) { struct mlx_softc *sc = (struct mlx_softc *)data; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* * Run a bus pause? */ if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when > 0) && (time_second >= sc->mlx_pause.mp_when)){ mlx_pause_action(sc); /* pause is running */ sc->mlx_pause.mp_when = 0; - sysbeep(500, hz); + sysbeep(500, SBT_1S); /* * Bus pause still running? */ } else if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when == 0)) { /* time to stop bus pause? */ if (time_second >= sc->mlx_pause.mp_howlong) { mlx_pause_action(sc); sc->mlx_pause.mp_which = 0; /* pause is complete */ - sysbeep(500, hz); + sysbeep(500, SBT_1S); } else { - sysbeep((time_second % 5) * 100 + 500, hz/8); + sysbeep((time_second % 5) * 100 + 500, SBT_1S / 8); } /* * Run normal periodic activities? */ } else if (time_second > (sc->mlx_lastpoll + 10)) { sc->mlx_lastpoll = time_second; /* * Check controller status. * * XXX Note that this may not actually launch a command in situations of high load. */ mlx_enquire(sc, (sc->mlx_iftype == MLX_IFTYPE_2) ? MLX_CMD_ENQUIRY_OLD : MLX_CMD_ENQUIRY, imax(sizeof(struct mlx_enquiry), sizeof(struct mlx_enquiry_old)), mlx_periodic_enquiry); /* * Check system drive status. * * XXX This might be better left to event-driven detection, eg. I/O to an offline * drive will detect it's offline, rebuilds etc. should detect the drive is back * online. */ mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(struct mlx_enq_sys_drive) * MLX_MAXDRIVES, mlx_periodic_enquiry); } /* get drive rebuild/check status */ /* XXX should check sc->mlx_background if this is only valid while in progress */ mlx_enquire(sc, MLX_CMD_REBUILDSTAT, sizeof(struct mlx_rebuild_stat), mlx_periodic_rebuild); /* deal with possibly-missed interrupts and timed-out commands */ mlx_done(sc, 1); /* reschedule another poll next second or so */ callout_reset(&sc->mlx_timeout, hz, mlx_periodic, sc); } /******************************************************************************** * Handle the result of an ENQUIRY command instigated by periodic status polling. */ static void mlx_periodic_enquiry(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* Command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "periodic enquiry failed - %s\n", mlx_diagnose_command(mc)); goto out; } /* respond to command */ switch(mc->mc_mailbox[0]) { /* * This is currently a bit fruitless, as we don't know how to extract the eventlog * pointer yet. */ case MLX_CMD_ENQUIRY_OLD: { struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data; struct mlx_enquiry_old *meo = (struct mlx_enquiry_old *)mc->mc_data; int i; /* convert data in-place to new format */ for (i = (sizeof(me->me_dead) / sizeof(me->me_dead[0])) - 1; i >= 0; i--) { me->me_dead[i].dd_chan = meo->me_dead[i].dd_chan; me->me_dead[i].dd_targ = meo->me_dead[i].dd_targ; } me->me_misc_flags = 0; me->me_rebuild_count = meo->me_rebuild_count; me->me_dead_count = meo->me_dead_count; me->me_critical_sd_count = meo->me_critical_sd_count; me->me_event_log_seq_num = 0; me->me_offline_sd_count = meo->me_offline_sd_count; me->me_max_commands = meo->me_max_commands; me->me_rebuild_flag = meo->me_rebuild_flag; me->me_fwmajor = meo->me_fwmajor; me->me_fwminor = meo->me_fwminor; me->me_status_flags = meo->me_status_flags; me->me_flash_age = meo->me_flash_age; for (i = (sizeof(me->me_drvsize) / sizeof(me->me_drvsize[0])) - 1; i >= 0; i--) { if (i > ((sizeof(meo->me_drvsize) / sizeof(meo->me_drvsize[0])) - 1)) { me->me_drvsize[i] = 0; /* drive beyond supported range */ } else { me->me_drvsize[i] = meo->me_drvsize[i]; } } me->me_num_sys_drvs = meo->me_num_sys_drvs; } /* FALLTHROUGH */ /* * Generic controller status update. We could do more with this than just * checking the event log. */ case MLX_CMD_ENQUIRY: { struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data; if (sc->mlx_currevent == -1) { /* initialise our view of the event log */ sc->mlx_currevent = sc->mlx_lastevent = me->me_event_log_seq_num; } else if ((me->me_event_log_seq_num != sc->mlx_lastevent) && !(sc->mlx_flags & MLX_EVENTLOG_BUSY)) { /* record where current events are up to */ sc->mlx_currevent = me->me_event_log_seq_num; debug(1, "event log pointer was %d, now %d\n", sc->mlx_lastevent, sc->mlx_currevent); /* mark the event log as busy */ sc->mlx_flags |= MLX_EVENTLOG_BUSY; /* drain new eventlog entries */ mlx_periodic_eventlog_poll(sc); } break; } case MLX_CMD_ENQSYSDRIVE: { struct mlx_enq_sys_drive *mes = (struct mlx_enq_sys_drive *)mc->mc_data; struct mlx_sysdrive *dr; int i; for (i = 0, dr = &sc->mlx_sysdrive[0]; (i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff); i++) { /* has state been changed by controller? */ if (dr->ms_state != mes[i].sd_state) { switch(mes[i].sd_state) { case MLX_SYSD_OFFLINE: device_printf(dr->ms_disk, "drive offline\n"); break; case MLX_SYSD_ONLINE: device_printf(dr->ms_disk, "drive online\n"); break; case MLX_SYSD_CRITICAL: device_printf(dr->ms_disk, "drive critical\n"); break; } /* save new state */ dr->ms_state = mes[i].sd_state; } } break; } default: device_printf(sc->mlx_dev, "%s: unknown command 0x%x", __func__, mc->mc_mailbox[0]); break; } out: free(mc->mc_data, M_DEVBUF); mlx_releasecmd(mc); } static void mlx_eventlog_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_command *mc; mc = (struct mlx_command *)arg; mlx_setup_dmamap(mc, segs, nsegments, error); /* build the command to get one entry */ mlx_make_type3(mc, MLX_CMD_LOGOP, MLX_LOGOP_GET, 1, mc->mc_sc->mlx_lastevent, 0, 0, mc->mc_dataphys, 0); mc->mc_complete = mlx_periodic_eventlog_respond; mc->mc_private = mc; /* start the command */ if (mlx_start(mc) != 0) { mlx_releasecmd(mc); free(mc->mc_data, M_DEVBUF); mc->mc_data = NULL; } } /******************************************************************************** * Instigate a poll for one event log message on (sc). * We only poll for one message at a time, to keep our command usage down. */ static void mlx_periodic_eventlog_poll(struct mlx_softc *sc) { struct mlx_command *mc; void *result = NULL; int error = 0; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 1; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* allocate the response structure */ if ((result = malloc(/*sizeof(struct mlx_eventlog_entry)*/1024, M_DEVBUF, M_NOWAIT)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* map the command so the controller can see it */ mc->mc_data = result; mc->mc_length = /*sizeof(struct mlx_eventlog_entry)*/1024; error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_eventlog_cb, mc, BUS_DMA_NOWAIT); out: if (error != 0) { if (mc != NULL) mlx_releasecmd(mc); if ((result != NULL) && (mc->mc_data != NULL)) free(result, M_DEVBUF); } } /******************************************************************************** * Handle the result of polling for a log message, generate diagnostic output. * If this wasn't the last message waiting for us, we'll go collect another. */ static char *mlx_sense_messages[] = { "because write recovery failed", "because of SCSI bus reset failure", "because of double check condition", "because it was removed", "because of gross error on SCSI chip", "because of bad tag returned from drive", "because of timeout on SCSI command", "because of reset SCSI command issued from system", "because busy or parity error count exceeded limit", "because of 'kill drive' command from system", "because of selection timeout", "due to SCSI phase sequence error", "due to unknown status" }; static void mlx_periodic_eventlog_respond(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; struct mlx_eventlog_entry *el = (struct mlx_eventlog_entry *)mc->mc_data; char *reason; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); sc->mlx_lastevent++; /* next message... */ if (mc->mc_status == 0) { /* handle event log message */ switch(el->el_type) { /* * This is the only sort of message we understand at the moment. * The tests here are probably incomplete. */ case MLX_LOGMSG_SENSE: /* sense data */ /* Mylex vendor-specific message indicating a drive was killed? */ if ((el->el_sensekey == 9) && (el->el_asc == 0x80)) { if (el->el_asq < nitems(mlx_sense_messages)) { reason = mlx_sense_messages[el->el_asq]; } else { reason = "for unknown reason"; } device_printf(sc->mlx_dev, "physical drive %d:%d killed %s\n", el->el_channel, el->el_target, reason); } /* SCSI drive was reset? */ if ((el->el_sensekey == 6) && (el->el_asc == 0x29)) { device_printf(sc->mlx_dev, "physical drive %d:%d reset\n", el->el_channel, el->el_target); } /* SCSI drive error? */ if (!((el->el_sensekey == 0) || ((el->el_sensekey == 2) && (el->el_asc == 0x04) && ((el->el_asq == 0x01) || (el->el_asq == 0x02))))) { device_printf(sc->mlx_dev, "physical drive %d:%d error log: sense = %d asc = %x asq = %x\n", el->el_channel, el->el_target, el->el_sensekey, el->el_asc, el->el_asq); device_printf(sc->mlx_dev, " info %4D csi %4D\n", el->el_information, ":", el->el_csi, ":"); } break; default: device_printf(sc->mlx_dev, "unknown log message type 0x%x\n", el->el_type); break; } } else { device_printf(sc->mlx_dev, "error reading message log - %s\n", mlx_diagnose_command(mc)); /* give up on all the outstanding messages, as we may have come unsynched */ sc->mlx_lastevent = sc->mlx_currevent; } /* dispose of command and data */ free(mc->mc_data, M_DEVBUF); mlx_releasecmd(mc); /* is there another message to obtain? */ if (sc->mlx_lastevent != sc->mlx_currevent) { mlx_periodic_eventlog_poll(sc); } else { /* clear log-busy status */ sc->mlx_flags &= ~MLX_EVENTLOG_BUSY; } } /******************************************************************************** * Handle check/rebuild operations in progress. */ static void mlx_periodic_rebuild(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; struct mlx_rebuild_status *mr = (struct mlx_rebuild_status *)mc->mc_data; MLX_IO_ASSERT_LOCKED(sc); switch(mc->mc_status) { case 0: /* operation running, update stats */ sc->mlx_rebuildstat = *mr; /* spontaneous rebuild/check? */ if (sc->mlx_background == 0) { sc->mlx_background = MLX_BACKGROUND_SPONTANEOUS; device_printf(sc->mlx_dev, "background check/rebuild operation started\n"); } break; case 0x0105: /* nothing running, finalise stats and report */ switch(sc->mlx_background) { case MLX_BACKGROUND_CHECK: device_printf(sc->mlx_dev, "consistency check completed\n"); /* XXX print drive? */ break; case MLX_BACKGROUND_REBUILD: device_printf(sc->mlx_dev, "drive rebuild completed\n"); /* XXX print channel/target? */ break; case MLX_BACKGROUND_SPONTANEOUS: default: /* if we have previously been non-idle, report the transition */ if (sc->mlx_rebuildstat.rs_code != MLX_REBUILDSTAT_IDLE) { device_printf(sc->mlx_dev, "background check/rebuild operation completed\n"); } } sc->mlx_background = 0; sc->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE; break; } free(mc->mc_data, M_DEVBUF); mlx_releasecmd(mc); } /******************************************************************************** ******************************************************************************** Channel Pause ******************************************************************************** ********************************************************************************/ /******************************************************************************** * It's time to perform a channel pause action for (sc), either start or stop * the pause. */ static void mlx_pause_action(struct mlx_softc *sc) { struct mlx_command *mc; int failsafe, i, command; MLX_IO_ASSERT_LOCKED(sc); /* What are we doing here? */ if (sc->mlx_pause.mp_when == 0) { command = MLX_CMD_STARTCHANNEL; failsafe = 0; } else { command = MLX_CMD_STOPCHANNEL; /* * Channels will always start again after the failsafe period, * which is specified in multiples of 30 seconds. * This constrains us to a maximum pause of 450 seconds. */ failsafe = ((sc->mlx_pause.mp_howlong - time_second) + 5) / 30; if (failsafe > 0xf) { failsafe = 0xf; sc->mlx_pause.mp_howlong = time_second + (0xf * 30) - 5; } } /* build commands for every channel requested */ for (i = 0; i < sc->mlx_enq2->me_actual_channels; i++) { if ((1 << i) & sc->mlx_pause.mp_which) { /* get ourselves a command buffer */ if ((mc = mlx_alloccmd(sc)) == NULL) goto fail; /* get a command slot */ mc->mc_flags |= MLX_CMD_PRIORITY; if (mlx_getslot(mc)) goto fail; /* build the command */ mlx_make_type2(mc, command, (failsafe << 4) | i, 0, 0, 0, 0, 0, 0, 0); mc->mc_complete = mlx_pause_done; mc->mc_private = sc; /* XXX not needed */ if (mlx_start(mc)) goto fail; /* command submitted OK */ return; fail: device_printf(sc->mlx_dev, "%s failed for channel %d\n", command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", i); if (mc != NULL) mlx_releasecmd(mc); } } } static void mlx_pause_done(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int command = mc->mc_mailbox[0]; int channel = mc->mc_mailbox[2] & 0xf; MLX_IO_ASSERT_LOCKED(sc); if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "%s command failed - %s\n", command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", mlx_diagnose_command(mc)); } else if (command == MLX_CMD_STOPCHANNEL) { device_printf(sc->mlx_dev, "channel %d pausing for %ld seconds\n", channel, (long)(sc->mlx_pause.mp_howlong - time_second)); } else { device_printf(sc->mlx_dev, "channel %d resuming\n", channel); } mlx_releasecmd(mc); } /******************************************************************************** ******************************************************************************** Command Submission ******************************************************************************** ********************************************************************************/ static void mlx_enquire_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_softc *sc; struct mlx_command *mc; mc = (struct mlx_command *)arg; if (error) return; mlx_setup_dmamap(mc, segs, nsegments, error); /* build an enquiry command */ sc = mc->mc_sc; mlx_make_type2(mc, mc->mc_command, 0, 0, 0, 0, 0, 0, mc->mc_dataphys, 0); /* do we want a completion callback? */ if (mc->mc_complete != NULL) { if ((error = mlx_start(mc)) != 0) return; } else { /* run the command in either polled or wait mode */ if ((sc->mlx_state & MLX_STATE_INTEN) ? mlx_wait_command(mc) : mlx_poll_command(mc)) return; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "ENQUIRY failed - %s\n", mlx_diagnose_command(mc)); return; } } } /******************************************************************************** * Perform an Enquiry command using a type-3 command buffer and a return a single * linear result buffer. If the completion function is specified, it will * be called with the completed command (and the result response will not be * valid until that point). Otherwise, the command will either be busy-waited * for (interrupts not enabled), or slept for. */ static void * mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, void (* complete)(struct mlx_command *mc)) { struct mlx_command *mc; void *result; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 1; result = NULL; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* allocate the response structure */ if ((result = malloc(bufsize, M_DEVBUF, M_NOWAIT)) == NULL) goto out; /* get a command slot */ mc->mc_flags |= MLX_CMD_PRIORITY | MLX_CMD_DATAOUT; if (mlx_getslot(mc)) goto out; /* map the command so the controller can see it */ mc->mc_data = result; mc->mc_length = bufsize; mc->mc_command = command; if (complete != NULL) { mc->mc_complete = complete; mc->mc_private = mc; } error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_enquire_cb, mc, BUS_DMA_NOWAIT); out: /* we got a command, but nobody else will free it */ if ((mc != NULL) && (mc->mc_complete == NULL)) mlx_releasecmd(mc); /* we got an error, and we allocated a result */ if ((error != 0) && (result != NULL)) { free(result, M_DEVBUF); result = NULL; } return(result); } /******************************************************************************** * Perform a Flush command on the nominated controller. * * May be called with interrupts enabled or disabled; will not return until * the flush operation completes or fails. */ static int mlx_flush(struct mlx_softc *sc) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 1; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* build a flush command */ mlx_make_type2(mc, MLX_CMD_FLUSH, 0, 0, 0, 0, 0, 0, 0, 0); /* can't assume that interrupts are going to work here, so play it safe */ if (mlx_poll_command(mc)) goto out; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "FLUSH failed - %s\n", mlx_diagnose_command(mc)); goto out; } error = 0; /* success */ out: if (mc != NULL) mlx_releasecmd(mc); return(error); } /******************************************************************************** * Start a background consistency check on (drive). * * May be called with interrupts enabled or disabled; will return as soon as the * operation has started or been refused. */ static int mlx_check(struct mlx_softc *sc, int drive) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 0x10000; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* build a checkasync command, set the "fix it" flag */ mlx_make_type2(mc, MLX_CMD_CHECKASYNC, 0, 0, 0, 0, 0, drive | 0x80, 0, 0); /* start the command and wait for it to be returned */ if (mlx_wait_command(mc)) goto out; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "CHECK ASYNC failed - %s\n", mlx_diagnose_command(mc)); } else { device_printf(sc->mlx_sysdrive[drive].ms_disk, "consistency check started"); } error = mc->mc_status; out: if (mc != NULL) mlx_releasecmd(mc); return(error); } /******************************************************************************** * Start a background rebuild of the physical drive at (channel),(target). * * May be called with interrupts enabled or disabled; will return as soon as the * operation has started or been refused. */ static int mlx_rebuild(struct mlx_softc *sc, int channel, int target) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 0x10000; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* build a checkasync command, set the "fix it" flag */ mlx_make_type2(mc, MLX_CMD_REBUILDASYNC, channel, target, 0, 0, 0, 0, 0, 0); /* start the command and wait for it to be returned */ if (mlx_wait_command(mc)) goto out; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "REBUILD ASYNC failed - %s\n", mlx_diagnose_command(mc)); } else { device_printf(sc->mlx_dev, "drive rebuild started for %d:%d\n", channel, target); } error = mc->mc_status; out: if (mc != NULL) mlx_releasecmd(mc); return(error); } /******************************************************************************** * Run the command (mc) and return when it completes. * * Interrupts need to be enabled; returns nonzero on error. */ static int mlx_wait_command(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int error, count; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); mc->mc_complete = NULL; mc->mc_private = mc; /* wake us when you're done */ if ((error = mlx_start(mc)) != 0) return(error); count = 0; /* XXX better timeout? */ while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) { mtx_sleep(mc->mc_private, &sc->mlx_io_lock, PRIBIO | PCATCH, "mlxwcmd", hz); } if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "command failed - %s\n", mlx_diagnose_command(mc)); return(EIO); } return(0); } /******************************************************************************** * Start the command (mc) and busy-wait for it to complete. * * Should only be used when interrupts can't be relied upon. Returns 0 on * success, nonzero on error. * Successfully completed commands are dequeued. */ static int mlx_poll_command(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int error, count; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); mc->mc_complete = NULL; mc->mc_private = NULL; /* we will poll for it */ if ((error = mlx_start(mc)) != 0) return(error); count = 0; do { /* poll for completion */ mlx_done(mc->mc_sc, 1); } while ((mc->mc_status == MLX_STATUS_BUSY) && (count++ < 15000000)); if (mc->mc_status != MLX_STATUS_BUSY) { TAILQ_REMOVE(&sc->mlx_work, mc, mc_link); return(0); } device_printf(sc->mlx_dev, "command failed - %s\n", mlx_diagnose_command(mc)); return(EIO); } void mlx_startio_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_command *mc; struct mlxd_softc *mlxd; struct mlx_softc *sc; struct bio *bp; int blkcount; int driveno; int cmd; mc = (struct mlx_command *)arg; mlx_setup_dmamap(mc, segs, nsegments, error); sc = mc->mc_sc; bp = mc->mc_private; if (bp->bio_cmd == BIO_READ) { mc->mc_flags |= MLX_CMD_DATAIN; cmd = MLX_CMD_READSG; } else { mc->mc_flags |= MLX_CMD_DATAOUT; cmd = MLX_CMD_WRITESG; } /* build a suitable I/O command (assumes 512-byte rounded transfers) */ mlxd = bp->bio_disk->d_drv1; driveno = mlxd->mlxd_drive - sc->mlx_sysdrive; blkcount = howmany(bp->bio_bcount, MLX_BLKSIZE); if ((bp->bio_pblkno + blkcount) > sc->mlx_sysdrive[driveno].ms_size) device_printf(sc->mlx_dev, "I/O beyond end of unit (%lld,%d > %lu)\n", (long long)bp->bio_pblkno, blkcount, (u_long)sc->mlx_sysdrive[driveno].ms_size); /* * Build the I/O command. Note that the SG list type bits are set to zero, * denoting the format of SG list that we are using. */ if (sc->mlx_iftype == MLX_IFTYPE_2) { mlx_make_type1(mc, (cmd == MLX_CMD_WRITESG) ? MLX_CMD_WRITESG_OLD : MLX_CMD_READSG_OLD, blkcount & 0xff, /* xfer length low byte */ bp->bio_pblkno, /* physical block number */ driveno, /* target drive number */ mc->mc_sgphys, /* location of SG list */ mc->mc_nsgent & 0x3f); /* size of SG list */ } else { mlx_make_type5(mc, cmd, blkcount & 0xff, /* xfer length low byte */ (driveno << 3) | ((blkcount >> 8) & 0x07), /* target+length high 3 bits */ bp->bio_pblkno, /* physical block number */ mc->mc_sgphys, /* location of SG list */ mc->mc_nsgent & 0x3f); /* size of SG list */ } /* try to give command to controller */ if (mlx_start(mc) != 0) { /* fail the command */ mc->mc_status = MLX_STATUS_WEDGED; mlx_completeio(mc); } sc->mlx_state &= ~MLX_STATE_QFROZEN; } /******************************************************************************** * Pull as much work off the softc's work queue as possible and give it to the * controller. Leave a couple of slots free for emergencies. */ static void mlx_startio(struct mlx_softc *sc) { struct mlx_command *mc; struct bio *bp; int error; MLX_IO_ASSERT_LOCKED(sc); /* spin until something prevents us from doing any work */ for (;;) { if (sc->mlx_state & MLX_STATE_QFROZEN) break; /* see if there's work to be done */ if ((bp = bioq_first(&sc->mlx_bioq)) == NULL) break; /* get a command */ if ((mc = mlx_alloccmd(sc)) == NULL) break; /* get a slot for the command */ if (mlx_getslot(mc) != 0) { mlx_releasecmd(mc); break; } /* get the buf containing our work */ bioq_remove(&sc->mlx_bioq, bp); sc->mlx_waitbufs--; /* connect the buf to the command */ mc->mc_complete = mlx_completeio; mc->mc_private = bp; mc->mc_data = bp->bio_data; mc->mc_length = bp->bio_bcount; /* map the command so the controller can work with it */ error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_startio_cb, mc, 0); if (error == EINPROGRESS) { sc->mlx_state |= MLX_STATE_QFROZEN; break; } } } /******************************************************************************** * Handle completion of an I/O command. */ static void mlx_completeio(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; struct bio *bp = mc->mc_private; struct mlxd_softc *mlxd = bp->bio_disk->d_drv1; MLX_IO_ASSERT_LOCKED(sc); if (mc->mc_status != MLX_STATUS_OK) { /* could be more verbose here? */ bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; switch(mc->mc_status) { case MLX_STATUS_RDWROFFLINE: /* system drive has gone offline */ device_printf(mlxd->mlxd_dev, "drive offline\n"); /* should signal this with a return code */ mlxd->mlxd_drive->ms_state = MLX_SYSD_OFFLINE; break; default: /* other I/O error */ device_printf(sc->mlx_dev, "I/O error - %s\n", mlx_diagnose_command(mc)); #if 0 device_printf(sc->mlx_dev, " b_bcount %ld blkcount %ld b_pblkno %d\n", bp->bio_bcount, bp->bio_bcount / MLX_BLKSIZE, bp->bio_pblkno); device_printf(sc->mlx_dev, " %13D\n", mc->mc_mailbox, " "); #endif break; } } mlx_releasecmd(mc); mlxd_intr(bp); } void mlx_user_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_usercommand *mu; struct mlx_command *mc; struct mlx_dcdb *dcdb; mc = (struct mlx_command *)arg; if (error) return; mlx_setup_dmamap(mc, segs, nsegments, error); mu = (struct mlx_usercommand *)mc->mc_private; dcdb = NULL; /* * If this is a passthrough SCSI command, the DCDB is packed at the * beginning of the data area. Fix up the DCDB to point to the correct * physical address and override any bufptr supplied by the caller since * we know what it's meant to be. */ if (mc->mc_mailbox[0] == MLX_CMD_DIRECT_CDB) { dcdb = (struct mlx_dcdb *)mc->mc_data; dcdb->dcdb_physaddr = mc->mc_dataphys + sizeof(*dcdb); mu->mu_bufptr = 8; } /* * If there's a data buffer, fix up the command's buffer pointer. */ if (mu->mu_datasize > 0) { mc->mc_mailbox[mu->mu_bufptr ] = mc->mc_dataphys & 0xff; mc->mc_mailbox[mu->mu_bufptr + 1] = (mc->mc_dataphys >> 8) & 0xff; mc->mc_mailbox[mu->mu_bufptr + 2] = (mc->mc_dataphys >> 16) & 0xff; mc->mc_mailbox[mu->mu_bufptr + 3] = (mc->mc_dataphys >> 24) & 0xff; } debug(0, "command fixup"); /* submit the command and wait */ if (mlx_wait_command(mc) != 0) return; } /******************************************************************************** * Take a command from user-space and try to run it. * * XXX Note that this can't perform very much in the way of error checking, and * as such, applications _must_ be considered trustworthy. * XXX Commands using S/G for data are not supported. */ static int mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu) { struct mlx_command *mc; void *kbuf; int error; debug_called(0); kbuf = NULL; mc = NULL; error = ENOMEM; /* get ourselves a command and copy in from user space */ MLX_IO_LOCK(sc); if ((mc = mlx_alloccmd(sc)) == NULL) { MLX_IO_UNLOCK(sc); return(error); } bcopy(mu->mu_command, mc->mc_mailbox, sizeof(mc->mc_mailbox)); debug(0, "got command buffer"); /* * if we need a buffer for data transfer, allocate one and copy in its * initial contents */ if (mu->mu_datasize > 0) { if (mu->mu_datasize > MLX_MAXPHYS) { error = EINVAL; goto out; } MLX_IO_UNLOCK(sc); if (((kbuf = malloc(mu->mu_datasize, M_DEVBUF, M_WAITOK)) == NULL) || (error = copyin(mu->mu_buf, kbuf, mu->mu_datasize))) { MLX_IO_LOCK(sc); goto out; } MLX_IO_LOCK(sc); debug(0, "got kernel buffer"); } /* get a command slot */ if (mlx_getslot(mc)) goto out; debug(0, "got a slot"); if (mu->mu_datasize > 0) { /* range check the pointer to physical buffer address */ if ((mu->mu_bufptr < 0) || (mu->mu_bufptr > (sizeof(mu->mu_command) - sizeof(u_int32_t)))) { error = EINVAL; goto out; } } /* map the command so the controller can see it */ mc->mc_data = kbuf; mc->mc_length = mu->mu_datasize; mc->mc_private = mu; error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_user_cb, mc, BUS_DMA_NOWAIT); if (error) goto out; /* copy out status and data */ mu->mu_status = mc->mc_status; if (mu->mu_datasize > 0) { MLX_IO_UNLOCK(sc); error = copyout(kbuf, mu->mu_buf, mu->mu_datasize); MLX_IO_LOCK(sc); } out: mlx_releasecmd(mc); MLX_IO_UNLOCK(sc); if (kbuf != NULL) free(kbuf, M_DEVBUF); return(error); } /******************************************************************************** ******************************************************************************** Command I/O to Controller ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Find a free command slot for (mc). * * Don't hand out a slot to a normal-priority command unless there are at least * 4 slots free for priority commands. */ static int mlx_getslot(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int slot, limit; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* * Enforce slot-usage limit, if we have the required information. */ if (sc->mlx_enq2 != NULL) { limit = sc->mlx_enq2->me_max_commands; } else { limit = 2; } if (sc->mlx_busycmds >= ((mc->mc_flags & MLX_CMD_PRIORITY) ? limit : limit - 4)) return(EBUSY); /* * Allocate an outstanding command slot * * XXX linear search is slow */ for (slot = 0; slot < limit; slot++) { debug(2, "try slot %d", slot); if (sc->mlx_busycmd[slot] == NULL) break; } if (slot < limit) { sc->mlx_busycmd[slot] = mc; sc->mlx_busycmds++; } /* out of slots? */ if (slot >= limit) return(EBUSY); debug(2, "got slot %d", slot); mc->mc_slot = slot; return(0); } /******************************************************************************** * Map/unmap (mc)'s data in the controller's addressable space. */ static void mlx_setup_dmamap(struct mlx_command *mc, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_softc *sc = mc->mc_sc; struct mlx_sgentry *sg; int i; debug_called(1); /* XXX should be unnecessary */ if (sc->mlx_enq2 && (nsegments > sc->mlx_enq2->me_max_sg)) panic("MLX: too many s/g segments (%d, max %d)", nsegments, sc->mlx_enq2->me_max_sg); /* get base address of s/g table */ sg = sc->mlx_sgtable + (mc->mc_slot * MLX_NSEG); /* save s/g table information in command */ mc->mc_nsgent = nsegments; mc->mc_sgphys = sc->mlx_sgbusaddr + (mc->mc_slot * MLX_NSEG * sizeof(struct mlx_sgentry)); mc->mc_dataphys = segs[0].ds_addr; /* populate s/g table */ for (i = 0; i < nsegments; i++, sg++) { sg->sg_addr = segs[i].ds_addr; sg->sg_count = segs[i].ds_len; } /* Make sure the buffers are visible on the bus. */ if (mc->mc_flags & MLX_CMD_DATAIN) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREREAD); if (mc->mc_flags & MLX_CMD_DATAOUT) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREWRITE); } static void mlx_unmapcmd(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; debug_called(1); /* if the command involved data at all */ if (mc->mc_data != NULL) { if (mc->mc_flags & MLX_CMD_DATAIN) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTREAD); if (mc->mc_flags & MLX_CMD_DATAOUT) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mlx_buffer_dmat, mc->mc_dmamap); } } /******************************************************************************** * Try to deliver (mc) to the controller. * * Can be called at any interrupt level, with or without interrupts enabled. */ static int mlx_start(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int i; debug_called(1); /* save the slot number as ident so we can handle this command when complete */ mc->mc_mailbox[0x1] = mc->mc_slot; /* mark the command as currently being processed */ mc->mc_status = MLX_STATUS_BUSY; /* set a default 60-second timeout XXX tunable? XXX not currently used */ mc->mc_timeout = time_second + 60; /* spin waiting for the mailbox */ for (i = 100000; i > 0; i--) { if (sc->mlx_tryqueue(sc, mc)) { /* move command to work queue */ TAILQ_INSERT_TAIL(&sc->mlx_work, mc, mc_link); return (0); } else if (i > 1) mlx_done(sc, 0); } /* * We couldn't get the controller to take the command. Revoke the slot * that the command was given and return it with a bad status. */ sc->mlx_busycmd[mc->mc_slot] = NULL; device_printf(sc->mlx_dev, "controller wedged (not taking commands)\n"); mc->mc_status = MLX_STATUS_WEDGED; mlx_complete(sc); return(EIO); } /******************************************************************************** * Poll the controller (sc) for completed commands. * Update command status and free slots for reuse. If any slots were freed, * new commands may be posted. * * Returns nonzero if one or more commands were completed. */ static int mlx_done(struct mlx_softc *sc, int startio) { struct mlx_command *mc; int result; u_int8_t slot; u_int16_t status; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); result = 0; /* loop collecting completed commands */ for (;;) { /* poll for a completed command's identifier and status */ if (sc->mlx_findcomplete(sc, &slot, &status)) { result = 1; mc = sc->mlx_busycmd[slot]; /* find command */ if (mc != NULL) { /* paranoia */ if (mc->mc_status == MLX_STATUS_BUSY) { mc->mc_status = status; /* save status */ /* free slot for reuse */ sc->mlx_busycmd[slot] = NULL; sc->mlx_busycmds--; } else { device_printf(sc->mlx_dev, "duplicate done event for slot %d\n", slot); } } else { device_printf(sc->mlx_dev, "done event for nonbusy slot %d\n", slot); } } else { break; } } /* if we've completed any commands, try posting some more */ if (result && startio) mlx_startio(sc); /* handle completion and timeouts */ mlx_complete(sc); return(result); } /******************************************************************************** * Perform post-completion processing for commands on (sc). */ static void mlx_complete(struct mlx_softc *sc) { struct mlx_command *mc, *nc; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* scan the list of busy/done commands */ mc = TAILQ_FIRST(&sc->mlx_work); while (mc != NULL) { nc = TAILQ_NEXT(mc, mc_link); /* Command has been completed in some fashion */ if (mc->mc_status != MLX_STATUS_BUSY) { /* unmap the command's data buffer */ mlx_unmapcmd(mc); /* * Does the command have a completion handler? */ if (mc->mc_complete != NULL) { /* remove from list and give to handler */ TAILQ_REMOVE(&sc->mlx_work, mc, mc_link); mc->mc_complete(mc); /* * Is there a sleeper waiting on this command? */ } else if (mc->mc_private != NULL) { /* sleeping caller wants to know about it */ /* remove from list and wake up sleeper */ TAILQ_REMOVE(&sc->mlx_work, mc, mc_link); wakeup_one(mc->mc_private); /* * Leave the command for a caller that's polling for it. */ } else { } } mc = nc; } } /******************************************************************************** ******************************************************************************** Command Buffer Management ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Get a new command buffer. * * This may return NULL in low-memory cases. * * Note that using malloc() is expensive (the command buffer is << 1 page) but * necessary if we are to be a loadable module before the zone allocator is fixed. * * If possible, we recycle a command buffer that's been used before. * * XXX Note that command buffers are not cleaned out - it is the caller's * responsibility to ensure that all required fields are filled in before * using a buffer. */ static struct mlx_command * mlx_alloccmd(struct mlx_softc *sc) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); if ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link); /* allocate a new command buffer? */ if (mc == NULL) { mc = (struct mlx_command *)malloc(sizeof(*mc), M_DEVBUF, M_NOWAIT | M_ZERO); if (mc != NULL) { mc->mc_sc = sc; error = bus_dmamap_create(sc->mlx_buffer_dmat, 0, &mc->mc_dmamap); if (error) { free(mc, M_DEVBUF); return(NULL); } } } return(mc); } /******************************************************************************** * Release a command buffer for recycling. * * XXX It might be a good idea to limit the number of commands we save for reuse * if it's shown that this list bloats out massively. */ static void mlx_releasecmd(struct mlx_command *mc) { debug_called(1); MLX_IO_ASSERT_LOCKED(mc->mc_sc); TAILQ_INSERT_HEAD(&mc->mc_sc->mlx_freecmds, mc, mc_link); } /******************************************************************************** * Permanently discard a command buffer. */ static void mlx_freecmd(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; debug_called(1); bus_dmamap_destroy(sc->mlx_buffer_dmat, mc->mc_dmamap); free(mc, M_DEVBUF); } /******************************************************************************** ******************************************************************************** Type 3 interface accessor methods ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure * (the controller is not ready to take a command). */ static int mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) { int i; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* ready for our command? */ if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_FULL)) { /* copy mailbox data to window */ for (i = 0; i < 13; i++) MLX_V3_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); /* post command */ MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_FULL); return(1); } return(0); } /******************************************************************************** * See if a command has been completed, if so acknowledge its completion * and recover the slot number and status code. */ static int mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) { debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* status available? */ if (MLX_V3_GET_ODBR(sc) & MLX_V3_ODB_SAVAIL) { *slot = MLX_V3_GET_STATUS_IDENT(sc); /* get command identifier */ *status = MLX_V3_GET_STATUS(sc); /* get status */ /* acknowledge completion */ MLX_V3_PUT_ODBR(sc, MLX_V3_ODB_SAVAIL); MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_SACK); return(1); } return(0); } /******************************************************************************** * Enable/disable interrupts as requested. (No acknowledge required) */ static void mlx_v3_intaction(struct mlx_softc *sc, int action) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); switch(action) { case MLX_INTACTION_DISABLE: MLX_V3_PUT_IER(sc, 0); sc->mlx_state &= ~MLX_STATE_INTEN; break; case MLX_INTACTION_ENABLE: MLX_V3_PUT_IER(sc, 1); sc->mlx_state |= MLX_STATE_INTEN; break; } } /******************************************************************************** * Poll for firmware error codes during controller initialisation. * Returns 0 if initialisation is complete, 1 if still in progress but no * error has been fetched, 2 if an error has been retrieved. */ static int mlx_v3_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first) { u_int8_t fwerror; debug_called(2); /* first time around, clear any hardware completion status */ if (first) { MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_SACK); DELAY(1000); } /* init in progress? */ if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_INIT_BUSY)) return(0); /* test error value */ fwerror = MLX_V3_GET_FWERROR(sc); if (!(fwerror & MLX_V3_FWERROR_PEND)) return(1); /* mask status pending bit, fetch status */ *error = fwerror & ~MLX_V3_FWERROR_PEND; *param1 = MLX_V3_GET_FWERROR_PARAM1(sc); *param2 = MLX_V3_GET_FWERROR_PARAM2(sc); /* acknowledge */ MLX_V3_PUT_FWERROR(sc, 0); return(2); } /******************************************************************************** ******************************************************************************** Type 4 interface accessor methods ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure * (the controller is not ready to take a command). */ static int mlx_v4_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) { int i; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* ready for our command? */ if (!(MLX_V4_GET_IDBR(sc) & MLX_V4_IDB_FULL)) { /* copy mailbox data to window */ for (i = 0; i < 13; i++) MLX_V4_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); /* memory-mapped controller, so issue a write barrier to ensure the mailbox is filled */ bus_barrier(sc->mlx_mem, MLX_V4_MAILBOX, MLX_V4_MAILBOX_LENGTH, BUS_SPACE_BARRIER_WRITE); /* post command */ MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_HWMBOX_CMD); return(1); } return(0); } /******************************************************************************** * See if a command has been completed, if so acknowledge its completion * and recover the slot number and status code. */ static int mlx_v4_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) { debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* status available? */ if (MLX_V4_GET_ODBR(sc) & MLX_V4_ODB_HWSAVAIL) { *slot = MLX_V4_GET_STATUS_IDENT(sc); /* get command identifier */ *status = MLX_V4_GET_STATUS(sc); /* get status */ /* acknowledge completion */ MLX_V4_PUT_ODBR(sc, MLX_V4_ODB_HWMBOX_ACK); MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_SACK); return(1); } return(0); } /******************************************************************************** * Enable/disable interrupts as requested. */ static void mlx_v4_intaction(struct mlx_softc *sc, int action) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); switch(action) { case MLX_INTACTION_DISABLE: MLX_V4_PUT_IER(sc, MLX_V4_IER_MASK | MLX_V4_IER_DISINT); sc->mlx_state &= ~MLX_STATE_INTEN; break; case MLX_INTACTION_ENABLE: MLX_V4_PUT_IER(sc, MLX_V4_IER_MASK & ~MLX_V4_IER_DISINT); sc->mlx_state |= MLX_STATE_INTEN; break; } } /******************************************************************************** * Poll for firmware error codes during controller initialisation. * Returns 0 if initialisation is complete, 1 if still in progress but no * error has been fetched, 2 if an error has been retrieved. */ static int mlx_v4_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first) { u_int8_t fwerror; debug_called(2); /* first time around, clear any hardware completion status */ if (first) { MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_SACK); DELAY(1000); } /* init in progress? */ if (!(MLX_V4_GET_IDBR(sc) & MLX_V4_IDB_INIT_BUSY)) return(0); /* test error value */ fwerror = MLX_V4_GET_FWERROR(sc); if (!(fwerror & MLX_V4_FWERROR_PEND)) return(1); /* mask status pending bit, fetch status */ *error = fwerror & ~MLX_V4_FWERROR_PEND; *param1 = MLX_V4_GET_FWERROR_PARAM1(sc); *param2 = MLX_V4_GET_FWERROR_PARAM2(sc); /* acknowledge */ MLX_V4_PUT_FWERROR(sc, 0); return(2); } /******************************************************************************** ******************************************************************************** Type 5 interface accessor methods ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure * (the controller is not ready to take a command). */ static int mlx_v5_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) { int i; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* ready for our command? */ if (MLX_V5_GET_IDBR(sc) & MLX_V5_IDB_EMPTY) { /* copy mailbox data to window */ for (i = 0; i < 13; i++) MLX_V5_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); /* post command */ MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_HWMBOX_CMD); return(1); } return(0); } /******************************************************************************** * See if a command has been completed, if so acknowledge its completion * and recover the slot number and status code. */ static int mlx_v5_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) { debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* status available? */ if (MLX_V5_GET_ODBR(sc) & MLX_V5_ODB_HWSAVAIL) { *slot = MLX_V5_GET_STATUS_IDENT(sc); /* get command identifier */ *status = MLX_V5_GET_STATUS(sc); /* get status */ /* acknowledge completion */ MLX_V5_PUT_ODBR(sc, MLX_V5_ODB_HWMBOX_ACK); MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_SACK); return(1); } return(0); } /******************************************************************************** * Enable/disable interrupts as requested. */ static void mlx_v5_intaction(struct mlx_softc *sc, int action) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); switch(action) { case MLX_INTACTION_DISABLE: MLX_V5_PUT_IER(sc, 0xff & MLX_V5_IER_DISINT); sc->mlx_state &= ~MLX_STATE_INTEN; break; case MLX_INTACTION_ENABLE: MLX_V5_PUT_IER(sc, 0xff & ~MLX_V5_IER_DISINT); sc->mlx_state |= MLX_STATE_INTEN; break; } } /******************************************************************************** * Poll for firmware error codes during controller initialisation. * Returns 0 if initialisation is complete, 1 if still in progress but no * error has been fetched, 2 if an error has been retrieved. */ static int mlx_v5_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first) { u_int8_t fwerror; debug_called(2); /* first time around, clear any hardware completion status */ if (first) { MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_SACK); DELAY(1000); } /* init in progress? */ if (MLX_V5_GET_IDBR(sc) & MLX_V5_IDB_INIT_DONE) return(0); /* test for error value */ fwerror = MLX_V5_GET_FWERROR(sc); if (!(fwerror & MLX_V5_FWERROR_PEND)) return(1); /* mask status pending bit, fetch status */ *error = fwerror & ~MLX_V5_FWERROR_PEND; *param1 = MLX_V5_GET_FWERROR_PARAM1(sc); *param2 = MLX_V5_GET_FWERROR_PARAM2(sc); /* acknowledge */ MLX_V5_PUT_FWERROR(sc, 0xff); return(2); } /******************************************************************************** ******************************************************************************** Debugging ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Return a status message describing (mc) */ static char *mlx_status_messages[] = { "normal completion", /* 00 */ "irrecoverable data error", /* 01 */ "drive does not exist, or is offline", /* 02 */ "attempt to write beyond end of drive", /* 03 */ "bad data encountered", /* 04 */ "invalid log entry request", /* 05 */ "attempt to rebuild online drive", /* 06 */ "new disk failed during rebuild", /* 07 */ "invalid channel/target", /* 08 */ "rebuild/check already in progress", /* 09 */ "one or more disks are dead", /* 10 */ "invalid or non-redundant drive", /* 11 */ "channel is busy", /* 12 */ "channel is not stopped", /* 13 */ "rebuild successfully terminated", /* 14 */ "unsupported command", /* 15 */ "check condition received", /* 16 */ "device is busy", /* 17 */ "selection or command timeout", /* 18 */ "command terminated abnormally", /* 19 */ "" }; static struct { int command; u_int16_t status; int msg; } mlx_messages[] = { {MLX_CMD_READSG, 0x0001, 1}, {MLX_CMD_READSG, 0x0002, 1}, {MLX_CMD_READSG, 0x0105, 3}, {MLX_CMD_READSG, 0x010c, 4}, {MLX_CMD_WRITESG, 0x0001, 1}, {MLX_CMD_WRITESG, 0x0002, 1}, {MLX_CMD_WRITESG, 0x0105, 3}, {MLX_CMD_READSG_OLD, 0x0001, 1}, {MLX_CMD_READSG_OLD, 0x0002, 1}, {MLX_CMD_READSG_OLD, 0x0105, 3}, {MLX_CMD_WRITESG_OLD, 0x0001, 1}, {MLX_CMD_WRITESG_OLD, 0x0002, 1}, {MLX_CMD_WRITESG_OLD, 0x0105, 3}, {MLX_CMD_LOGOP, 0x0105, 5}, {MLX_CMD_REBUILDASYNC, 0x0002, 6}, {MLX_CMD_REBUILDASYNC, 0x0004, 7}, {MLX_CMD_REBUILDASYNC, 0x0105, 8}, {MLX_CMD_REBUILDASYNC, 0x0106, 9}, {MLX_CMD_REBUILDASYNC, 0x0107, 14}, {MLX_CMD_CHECKASYNC, 0x0002, 10}, {MLX_CMD_CHECKASYNC, 0x0105, 11}, {MLX_CMD_CHECKASYNC, 0x0106, 9}, {MLX_CMD_STOPCHANNEL, 0x0106, 12}, {MLX_CMD_STOPCHANNEL, 0x0105, 8}, {MLX_CMD_STARTCHANNEL, 0x0005, 13}, {MLX_CMD_STARTCHANNEL, 0x0105, 8}, {MLX_CMD_DIRECT_CDB, 0x0002, 16}, {MLX_CMD_DIRECT_CDB, 0x0008, 17}, {MLX_CMD_DIRECT_CDB, 0x000e, 18}, {MLX_CMD_DIRECT_CDB, 0x000f, 19}, {MLX_CMD_DIRECT_CDB, 0x0105, 8}, {0, 0x0104, 14}, {-1, 0, 0} }; static char * mlx_diagnose_command(struct mlx_command *mc) { static char unkmsg[80]; int i; /* look up message in table */ for (i = 0; mlx_messages[i].command != -1; i++) if (((mc->mc_mailbox[0] == mlx_messages[i].command) || (mlx_messages[i].command == 0)) && (mc->mc_status == mlx_messages[i].status)) return(mlx_status_messages[mlx_messages[i].msg]); sprintf(unkmsg, "unknown response 0x%x for command 0x%x", (int)mc->mc_status, (int)mc->mc_mailbox[0]); return(unkmsg); } /******************************************************************************* * Print a string describing the controller (sc) */ static struct { int hwid; char *name; } mlx_controller_names[] = { {0x01, "960P/PD"}, {0x02, "960PL"}, {0x10, "960PG"}, {0x11, "960PJ"}, {0x12, "960PR"}, {0x13, "960PT"}, {0x14, "960PTL0"}, {0x15, "960PRL"}, {0x16, "960PTL1"}, {0x20, "1164PVX"}, {-1, NULL} }; static void mlx_describe_controller(struct mlx_softc *sc) { static char buf[80]; char *model; int i; for (i = 0, model = NULL; mlx_controller_names[i].name != NULL; i++) { if ((sc->mlx_enq2->me_hardware_id & 0xff) == mlx_controller_names[i].hwid) { model = mlx_controller_names[i].name; break; } } if (model == NULL) { sprintf(buf, " model 0x%x", sc->mlx_enq2->me_hardware_id & 0xff); model = buf; } device_printf(sc->mlx_dev, "DAC%s, %d channel%s, firmware %d.%02d-%c-%02d, %dMB RAM\n", model, sc->mlx_enq2->me_actual_channels, sc->mlx_enq2->me_actual_channels > 1 ? "s" : "", sc->mlx_enq2->me_firmware_id & 0xff, (sc->mlx_enq2->me_firmware_id >> 8) & 0xff, (sc->mlx_enq2->me_firmware_id >> 24) & 0xff, (sc->mlx_enq2->me_firmware_id >> 16) & 0xff, sc->mlx_enq2->me_mem_size / (1024 * 1024)); if (bootverbose) { device_printf(sc->mlx_dev, " Hardware ID 0x%08x\n", sc->mlx_enq2->me_hardware_id); device_printf(sc->mlx_dev, " Firmware ID 0x%08x\n", sc->mlx_enq2->me_firmware_id); device_printf(sc->mlx_dev, " Configured/Actual channels %d/%d\n", sc->mlx_enq2->me_configured_channels, sc->mlx_enq2->me_actual_channels); device_printf(sc->mlx_dev, " Max Targets %d\n", sc->mlx_enq2->me_max_targets); device_printf(sc->mlx_dev, " Max Tags %d\n", sc->mlx_enq2->me_max_tags); device_printf(sc->mlx_dev, " Max System Drives %d\n", sc->mlx_enq2->me_max_sys_drives); device_printf(sc->mlx_dev, " Max Arms %d\n", sc->mlx_enq2->me_max_arms); device_printf(sc->mlx_dev, " Max Spans %d\n", sc->mlx_enq2->me_max_spans); device_printf(sc->mlx_dev, " DRAM/cache/flash/NVRAM size %d/%d/%d/%d\n", sc->mlx_enq2->me_mem_size, sc->mlx_enq2->me_cache_size, sc->mlx_enq2->me_flash_size, sc->mlx_enq2->me_nvram_size); device_printf(sc->mlx_dev, " DRAM type %d\n", sc->mlx_enq2->me_mem_type); device_printf(sc->mlx_dev, " Clock Speed %dns\n", sc->mlx_enq2->me_clock_speed); device_printf(sc->mlx_dev, " Hardware Speed %dns\n", sc->mlx_enq2->me_hardware_speed); device_printf(sc->mlx_dev, " Max Commands %d\n", sc->mlx_enq2->me_max_commands); device_printf(sc->mlx_dev, " Max SG Entries %d\n", sc->mlx_enq2->me_max_sg); device_printf(sc->mlx_dev, " Max DP %d\n", sc->mlx_enq2->me_max_dp); device_printf(sc->mlx_dev, " Max IOD %d\n", sc->mlx_enq2->me_max_iod); device_printf(sc->mlx_dev, " Max Comb %d\n", sc->mlx_enq2->me_max_comb); device_printf(sc->mlx_dev, " Latency %ds\n", sc->mlx_enq2->me_latency); device_printf(sc->mlx_dev, " SCSI Timeout %ds\n", sc->mlx_enq2->me_scsi_timeout); device_printf(sc->mlx_dev, " Min Free Lines %d\n", sc->mlx_enq2->me_min_freelines); device_printf(sc->mlx_dev, " Rate Constant %d\n", sc->mlx_enq2->me_rate_const); device_printf(sc->mlx_dev, " MAXBLK %d\n", sc->mlx_enq2->me_maxblk); device_printf(sc->mlx_dev, " Blocking Factor %d sectors\n", sc->mlx_enq2->me_blocking_factor); device_printf(sc->mlx_dev, " Cache Line Size %d blocks\n", sc->mlx_enq2->me_cacheline); device_printf(sc->mlx_dev, " SCSI Capability %s%dMHz, %d bit\n", sc->mlx_enq2->me_scsi_cap & (1<<4) ? "differential " : "", (1 << ((sc->mlx_enq2->me_scsi_cap >> 2) & 3)) * 10, 8 << (sc->mlx_enq2->me_scsi_cap & 0x3)); device_printf(sc->mlx_dev, " Firmware Build Number %d\n", sc->mlx_enq2->me_firmware_build); device_printf(sc->mlx_dev, " Fault Management Type %d\n", sc->mlx_enq2->me_fault_mgmt_type); device_printf(sc->mlx_dev, " Features %b\n", sc->mlx_enq2->me_firmware_features, "\20\4Background Init\3Read Ahead\2MORE\1Cluster\n"); } } /******************************************************************************* * Emit a string describing the firmware handshake status code, and return a flag * indicating whether the code represents a fatal error. * * Error code interpretations are from the Linux driver, and don't directly match * the messages printed by Mylex's BIOS. This may change if documentation on the * codes is forthcoming. */ static int mlx_fw_message(struct mlx_softc *sc, int error, int param1, int param2) { switch(error) { case 0x00: device_printf(sc->mlx_dev, "physical drive %d:%d not responding\n", param2, param1); break; case 0x08: /* we could be neater about this and give some indication when we receive more of them */ if (!(sc->mlx_flags & MLX_SPINUP_REPORTED)) { device_printf(sc->mlx_dev, "spinning up drives...\n"); sc->mlx_flags |= MLX_SPINUP_REPORTED; } break; case 0x30: device_printf(sc->mlx_dev, "configuration checksum error\n"); break; case 0x60: device_printf(sc->mlx_dev, "mirror race recovery failed\n"); break; case 0x70: device_printf(sc->mlx_dev, "mirror race recovery in progress\n"); break; case 0x90: device_printf(sc->mlx_dev, "physical drive %d:%d COD mismatch\n", param2, param1); break; case 0xa0: device_printf(sc->mlx_dev, "logical drive installation aborted\n"); break; case 0xb0: device_printf(sc->mlx_dev, "mirror race on a critical system drive\n"); break; case 0xd0: device_printf(sc->mlx_dev, "new controller configuration found\n"); break; case 0xf0: device_printf(sc->mlx_dev, "FATAL MEMORY PARITY ERROR\n"); return(1); default: device_printf(sc->mlx_dev, "unknown firmware initialisation error %02x:%02x:%02x\n", error, param1, param2); break; } return(0); } /******************************************************************************** ******************************************************************************** Utility Functions ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Find the disk whose unit number is (unit) on this controller */ static struct mlx_sysdrive * mlx_findunit(struct mlx_softc *sc, int unit) { int i; /* search system drives */ MLX_CONFIG_ASSERT_LOCKED(sc); for (i = 0; i < MLX_MAXDRIVES; i++) { /* is this one attached? */ if (sc->mlx_sysdrive[i].ms_disk != 0) { /* is this the one? */ if (unit == device_get_unit(sc->mlx_sysdrive[i].ms_disk)) return(&sc->mlx_sysdrive[i]); } } return(NULL); } diff --git a/sys/dev/syscons/syscons.c b/sys/dev/syscons/syscons.c index 6a389604d3ab..6c349668410d 100644 --- a/sys/dev/syscons/syscons.c +++ b/sys/dev/syscons/syscons.c @@ -1,4368 +1,4373 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992-1998 Søren Schmidt * All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Sascha Wildner * * 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, * without modification, immediately at the beginning of the file. * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_syscons.h" #include "opt_splash.h" #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__arm__) || defined(__mips__) || defined(__powerpc__) #include #else #include #endif #if defined(__i386__) || defined(__amd64__) #include #include #endif #include #if defined(__amd64__) || defined(__i386__) #include #include #include #endif #include #include #include #include #define COLD 0 #define WARM 1 #define DEFAULT_BLANKTIME (5 * 60) /* 5 minutes */ #define MAX_BLANKTIME (7 * 24 * 60 * 60) /* 7 days!? */ #define KEYCODE_BS 0x0e /* "<-- Backspace" key, XXX */ /* NULL-safe version of "tty_opened()" */ #define tty_opened_ns(tp) ((tp) != NULL && tty_opened(tp)) static u_char sc_kattrtab[MAXCPU]; static int sc_console_unit = -1; static int sc_saver_keyb_only = 1; static scr_stat *sc_console; static struct consdev *sc_consptr; static void *sc_kts[MAXCPU]; static struct sc_term_sw *sc_ktsw; static scr_stat main_console; static struct tty *main_devs[MAXCONS]; static char init_done = COLD; static int shutdown_in_progress = FALSE; static int suspend_in_progress = FALSE; static char sc_malloc = FALSE; static int saver_mode = CONS_NO_SAVER; /* LKM/user saver */ static int run_scrn_saver = FALSE; /* should run the saver? */ static int enable_bell = TRUE; /* enable beeper */ #ifndef SC_DISABLE_REBOOT static int enable_reboot = TRUE; /* enable keyboard reboot */ #endif #ifndef SC_DISABLE_KDBKEY static int enable_kdbkey = TRUE; /* enable keyboard debug */ #endif static long scrn_blank_time = 0; /* screen saver timeout value */ #ifdef DEV_SPLASH static int scrn_blanked; /* # of blanked screen */ static int sticky_splash = FALSE; static void none_saver(sc_softc_t *sc, int blank) { } static void (*current_saver)(sc_softc_t *, int) = none_saver; #endif #ifdef SC_NO_SUSPEND_VTYSWITCH static int sc_no_suspend_vtswitch = 1; #else static int sc_no_suspend_vtswitch = 0; #endif static int sc_susp_scr; static SYSCTL_NODE(_hw, OID_AUTO, syscons, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "syscons"); static SYSCTL_NODE(_hw_syscons, OID_AUTO, saver, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "saver"); SYSCTL_INT(_hw_syscons_saver, OID_AUTO, keybonly, CTLFLAG_RW, &sc_saver_keyb_only, 0, "screen saver interrupted by input only"); SYSCTL_INT( _hw_syscons, OID_AUTO, bell, CTLFLAG_RW, &enable_bell, 0, "enable bell"); #ifndef SC_DISABLE_REBOOT SYSCTL_INT(_hw_syscons, OID_AUTO, kbd_reboot, CTLFLAG_RW | CTLFLAG_SECURE, &enable_reboot, 0, "enable keyboard reboot"); #endif #ifndef SC_DISABLE_KDBKEY SYSCTL_INT(_hw_syscons, OID_AUTO, kbd_debug, CTLFLAG_RW | CTLFLAG_SECURE, &enable_kdbkey, 0, "enable keyboard debug"); #endif SYSCTL_INT(_hw_syscons, OID_AUTO, sc_no_suspend_vtswitch, CTLFLAG_RWTUN, &sc_no_suspend_vtswitch, 0, "Disable VT switch before suspend."); #if !defined(SC_NO_FONT_LOADING) && defined(SC_DFLT_FONT) #include "font.h" #endif tsw_ioctl_t *sc_user_ioctl; static bios_values_t bios_value; static int enable_panic_key; SYSCTL_INT(_machdep, OID_AUTO, enable_panic_key, CTLFLAG_RW, &enable_panic_key, 0, "Enable panic via keypress specified in kbdmap(5)"); #define SC_CONSOLECTL 255 #define VTY_WCHAN(sc, vty) (&SC_DEV(sc, vty)) /* prototypes */ static int sc_allocate_keyboard(sc_softc_t *sc, int unit); static int scvidprobe(int unit, int flags, int cons); static int sckbdprobe(int unit, int flags, int cons); static void scmeminit(void *arg); static int scdevtounit(struct tty *tp); static kbd_callback_func_t sckbdevent; static void scinit(int unit, int flags); static scr_stat *sc_get_stat(struct tty *tp); static void scterm(int unit, int flags); static void scshutdown(void *, int); static void scsuspend(void *); static void scresume(void *); static u_int scgetc(sc_softc_t *sc, u_int flags, struct sc_cnstate *sp); static void sc_puts(scr_stat *scp, u_char *buf, int len); #define SCGETC_CN 1 #define SCGETC_NONBLOCK 2 static void sccnupdate(scr_stat *scp); static scr_stat *alloc_scp(sc_softc_t *sc, int vty); static void init_scp(sc_softc_t *sc, int vty, scr_stat *scp); static callout_func_t scrn_timer; static int and_region(int *s1, int *e1, int s2, int e2); static void scrn_update(scr_stat *scp, int show_cursor); #ifdef DEV_SPLASH static int scsplash_callback(int event, void *arg); static void scsplash_saver(sc_softc_t *sc, int show); static int add_scrn_saver(void (*this_saver)(sc_softc_t *, int)); static int remove_scrn_saver(void (*this_saver)(sc_softc_t *, int)); static int set_scrn_saver_mode( scr_stat *scp, int mode, u_char *pal, int border); static int restore_scrn_saver_mode(scr_stat *scp, int changemode); static void stop_scrn_saver(sc_softc_t *sc, void (*saver)(sc_softc_t *, int)); static int wait_scrn_saver_stop(sc_softc_t *sc); #define scsplash_stick(stick) (sticky_splash = (stick)) #else /* !DEV_SPLASH */ #define scsplash_stick(stick) #endif /* DEV_SPLASH */ static int do_switch_scr(sc_softc_t *sc, int s); static int vt_proc_alive(scr_stat *scp); static int signal_vt_rel(scr_stat *scp); static int signal_vt_acq(scr_stat *scp); static int finish_vt_rel(scr_stat *scp, int release, int *s); static int finish_vt_acq(scr_stat *scp); static void exchange_scr(sc_softc_t *sc); static void update_cursor_image(scr_stat *scp); static void change_cursor_shape(scr_stat *scp, int flags, int base, int height); static void update_font(scr_stat *); static int save_kbd_state(scr_stat *scp); static int update_kbd_state(scr_stat *scp, int state, int mask); static int update_kbd_leds(scr_stat *scp, int which); static int sc_kattr(void); static callout_func_t blink_screen; static struct tty *sc_alloc_tty(int, int); static cn_probe_t sc_cnprobe; static cn_init_t sc_cninit; static cn_term_t sc_cnterm; static cn_getc_t sc_cngetc; static cn_putc_t sc_cnputc; static cn_grab_t sc_cngrab; static cn_ungrab_t sc_cnungrab; CONSOLE_DRIVER(sc); static tsw_open_t sctty_open; static tsw_close_t sctty_close; static tsw_outwakeup_t sctty_outwakeup; static tsw_ioctl_t sctty_ioctl; static tsw_mmap_t sctty_mmap; static struct ttydevsw sc_ttydevsw = { .tsw_open = sctty_open, .tsw_close = sctty_close, .tsw_outwakeup = sctty_outwakeup, .tsw_ioctl = sctty_ioctl, .tsw_mmap = sctty_mmap, }; static d_ioctl_t consolectl_ioctl; static d_close_t consolectl_close; static struct cdevsw consolectl_devsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT | D_TRACKCLOSE, .d_ioctl = consolectl_ioctl, .d_close = consolectl_close, .d_name = "consolectl", }; /* ec -- emergency console. */ static u_int ec_scroffset; static void ec_putc(int c) { uintptr_t fb; u_short *scrptr; u_int ind; int attr, column, mysize, width, xsize, yborder, ysize; if (c < 0 || c > 0xff || c == '\a') return; if (sc_console == NULL) { #if !defined(__amd64__) && !defined(__i386__) return; #else /* * This is enough for ec_putc() to work very early on x86 * if the kernel starts in normal color text mode. */ #ifdef __amd64__ fb = KERNBASE + 0xb8000; #else /* __i386__ */ fb = pmap_get_map_low() + 0xb8000; #endif xsize = 80; ysize = 25; #endif } else { if (!ISTEXTSC(&main_console)) return; fb = main_console.sc->adp->va_window; xsize = main_console.xsize; ysize = main_console.ysize; } yborder = ysize / 5; scrptr = (u_short *)(void *)fb + xsize * yborder; mysize = xsize * (ysize - 2 * yborder); do { ind = ec_scroffset; column = ind % xsize; width = (c == '\b' ? -1 : c == '\t' ? (column + 8) & ~7 : c == '\r' ? -column : c == '\n' ? xsize - column : 1); if (width == 0 || (width < 0 && ind < -width)) return; } while (atomic_cmpset_rel_int(&ec_scroffset, ind, ind + width) == 0); if (c == '\b' || c == '\r') return; if (c == '\n') ind += xsize; /* XXX clearing from new pos is not atomic */ attr = sc_kattr(); if (c == '\t' || c == '\n') c = ' '; do scrptr[ind++ % mysize] = (attr << 8) | c; while (--width != 0); } int sc_probe_unit(int unit, int flags) { if (!vty_enabled(VTY_SC)) return ENXIO; if (!scvidprobe(unit, flags, FALSE)) { if (bootverbose) printf("%s%d: no video adapter found.\n", SC_DRIVER_NAME, unit); return ENXIO; } /* syscons will be attached even when there is no keyboard */ sckbdprobe(unit, flags, FALSE); return 0; } /* probe video adapters, return TRUE if found */ static int scvidprobe(int unit, int flags, int cons) { /* * Access the video adapter driver through the back door! * Video adapter drivers need to be configured before syscons. * However, when syscons is being probed as the low-level console, * they have not been initialized yet. We force them to initialize * themselves here. XXX */ vid_configure(cons ? VIO_PROBE_ONLY : 0); return (vid_find_adapter("*", unit) >= 0); } /* probe the keyboard, return TRUE if found */ static int sckbdprobe(int unit, int flags, int cons) { /* access the keyboard driver through the backdoor! */ kbd_configure(cons ? KB_CONF_PROBE_ONLY : 0); return (kbd_find_keyboard("*", unit) >= 0); } static char * adapter_name(video_adapter_t *adp) { static struct { int type; char *name[2]; } names[] = { { KD_MONO, { "MDA", "MDA" } }, { KD_HERCULES, { "Hercules", "Hercules" } }, { KD_CGA, { "CGA", "CGA" } }, { KD_EGA, { "EGA", "EGA (mono)" } }, { KD_VGA, { "VGA", "VGA (mono)" } }, { KD_TGA, { "TGA", "TGA" } }, { -1, { "Unknown", "Unknown" } }, }; int i; for (i = 0; names[i].type != -1; ++i) if (names[i].type == adp->va_type) break; return names[i].name[(adp->va_flags & V_ADP_COLOR) ? 0 : 1]; } static void sctty_outwakeup(struct tty *tp) { size_t len; u_char buf[PCBURST]; scr_stat *scp = sc_get_stat(tp); if (scp->status & SLKED || (scp == scp->sc->cur_scp && scp->sc->blink_in_progress)) return; for (;;) { len = ttydisc_getc(tp, buf, sizeof buf); if (len == 0) break; SC_VIDEO_LOCK(scp->sc); sc_puts(scp, buf, len); SC_VIDEO_UNLOCK(scp->sc); } } static struct tty * sc_alloc_tty(int index, int devnum) { struct sc_ttysoftc *stc; struct tty *tp; /* Allocate TTY object and softc to store unit number. */ stc = malloc(sizeof(struct sc_ttysoftc), M_DEVBUF, M_WAITOK); stc->st_index = index; stc->st_stat = NULL; tp = tty_alloc_mutex(&sc_ttydevsw, stc, &Giant); /* Create device node. */ tty_makedev(tp, NULL, "v%r", devnum); return (tp); } #ifdef SC_PIXEL_MODE static void sc_set_vesa_mode(scr_stat *scp, sc_softc_t *sc, int unit) { video_info_t info; u_char *font; int depth; int fontsize; int i; int vmode; vmode = 0; (void)resource_int_value("sc", unit, "vesa_mode", &vmode); if (vmode < M_VESA_BASE || vmode > M_VESA_MODE_MAX || vidd_get_info(sc->adp, vmode, &info) != 0 || !sc_support_pixel_mode(&info)) vmode = 0; /* * If the mode is unset or unsupported, search for an available * 800x600 graphics mode with the highest color depth. */ if (vmode == 0) { for (depth = 0, i = M_VESA_BASE; i <= M_VESA_MODE_MAX; i++) if (vidd_get_info(sc->adp, i, &info) == 0 && info.vi_width == 800 && info.vi_height == 600 && sc_support_pixel_mode(&info) && info.vi_depth > depth) { vmode = i; depth = info.vi_depth; } if (vmode == 0) return; vidd_get_info(sc->adp, vmode, &info); } #if !defined(SC_NO_FONT_LOADING) && defined(SC_DFLT_FONT) fontsize = info.vi_cheight; #else fontsize = scp->font_size; #endif if (fontsize < 14) fontsize = 8; else if (fontsize >= 16) fontsize = 16; else fontsize = 14; #ifndef SC_NO_FONT_LOADING switch (fontsize) { case 8: if ((sc->fonts_loaded & FONT_8) == 0) return; font = sc->font_8; break; case 14: if ((sc->fonts_loaded & FONT_14) == 0) return; font = sc->font_14; break; case 16: if ((sc->fonts_loaded & FONT_16) == 0) return; font = sc->font_16; break; } #else font = NULL; #endif #ifdef DEV_SPLASH if ((sc->flags & SC_SPLASH_SCRN) != 0) splash_term(sc->adp); #endif #ifndef SC_NO_HISTORY if (scp->history != NULL) { sc_vtb_append(&scp->vtb, 0, scp->history, scp->ypos * scp->xsize + scp->xpos); scp->history_pos = sc_vtb_tail(scp->history); } #endif vidd_set_mode(sc->adp, vmode); scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN); scp->status &= ~(GRAPHICS_MODE | MOUSE_VISIBLE); scp->xpixel = info.vi_width; scp->ypixel = info.vi_height; scp->xsize = scp->xpixel / 8; scp->ysize = scp->ypixel / fontsize; scp->xpos = 0; scp->ypos = scp->ysize - 1; scp->xoff = scp->yoff = 0; scp->font = font; scp->font_size = fontsize; scp->font_width = 8; scp->start = scp->xsize * scp->ysize - 1; scp->end = 0; scp->cursor_pos = scp->cursor_oldpos = scp->xsize * scp->xsize; scp->mode = sc->initial_mode = vmode; sc_vtb_init(&scp->scr, VTB_FRAMEBUFFER, scp->xsize, scp->ysize, (void *)sc->adp->va_window, FALSE); sc_alloc_scr_buffer(scp, FALSE, FALSE); sc_init_emulator(scp, NULL); #ifndef SC_NO_CUTPASTE sc_alloc_cut_buffer(scp, FALSE); #endif #ifndef SC_NO_HISTORY sc_alloc_history_buffer(scp, 0, 0, FALSE); #endif sc_set_border(scp, scp->border); sc_set_cursor_image(scp); scp->status &= ~UNKNOWN_MODE; #ifdef DEV_SPLASH if ((sc->flags & SC_SPLASH_SCRN) != 0) splash_init(sc->adp, scsplash_callback, sc); #endif } #endif int sc_attach_unit(int unit, int flags) { sc_softc_t *sc; scr_stat *scp; struct cdev *dev; void *oldts, *ts; int i, vc; if (!vty_enabled(VTY_SC)) return ENXIO; flags &= ~SC_KERNEL_CONSOLE; if (sc_console_unit == unit) { /* * If this unit is being used as the system console, we need to * adjust some variables and buffers before and after scinit(). */ /* assert(sc_console != NULL) */ flags |= SC_KERNEL_CONSOLE; scmeminit(NULL); scinit(unit, flags); if (sc_console->tsw->te_size > 0) { sc_ktsw = sc_console->tsw; /* assert(sc_console->ts != NULL); */ oldts = sc_console->ts; for (i = 0; i <= mp_maxid; i++) { ts = malloc(sc_console->tsw->te_size, M_DEVBUF, M_WAITOK | M_ZERO); (*sc_console->tsw->te_init)( sc_console, &ts, SC_TE_COLD_INIT); sc_console->ts = ts; (*sc_console->tsw->te_default_attr)(sc_console, sc_kattrtab[i], SC_KERNEL_CONS_REV_ATTR); sc_kts[i] = ts; } sc_console->ts = oldts; (*sc_console->tsw->te_default_attr)( sc_console, SC_NORM_ATTR, SC_NORM_REV_ATTR); } } else { scinit(unit, flags); } sc = sc_get_softc(unit, flags & SC_KERNEL_CONSOLE); sc->config = flags; callout_init(&sc->ctimeout, 0); callout_init(&sc->cblink, 0); scp = sc_get_stat(sc->dev[0]); if (sc_console == NULL) /* sc_console_unit < 0 */ sc_console = scp; #ifdef SC_PIXEL_MODE if ((sc->config & SC_VESAMODE) != 0) sc_set_vesa_mode(scp, sc, unit); #endif /* SC_PIXEL_MODE */ /* initialize cursor */ if (!ISGRAPHSC(scp)) update_cursor_image(scp); /* get screen update going */ scrn_timer(sc); /* set up the keyboard */ (void)kbdd_ioctl(sc->kbd, KDSKBMODE, (caddr_t)&scp->kbd_mode); update_kbd_state(scp, scp->status, LOCK_MASK); printf("%s%d: %s <%d virtual consoles, flags=0x%x>\n", SC_DRIVER_NAME, unit, adapter_name(sc->adp), sc->vtys, sc->config); if (bootverbose) { printf("%s%d:", SC_DRIVER_NAME, unit); if (sc->adapter >= 0) printf(" fb%d", sc->adapter); if (sc->kbd != NULL) printf(", kbd%d", sc->kbd->kb_index); if (scp->tsw) printf(", terminal emulator: %s (%s)", scp->tsw->te_name, scp->tsw->te_desc); printf("\n"); } /* Register suspend/resume/shutdown callbacks for the kernel console. */ if (sc_console_unit == unit) { EVENTHANDLER_REGISTER( power_suspend_early, scsuspend, NULL, EVENTHANDLER_PRI_ANY); EVENTHANDLER_REGISTER( power_resume, scresume, NULL, EVENTHANDLER_PRI_ANY); EVENTHANDLER_REGISTER( shutdown_pre_sync, scshutdown, NULL, SHUTDOWN_PRI_DEFAULT); } for (vc = 0; vc < sc->vtys; vc++) { if (sc->dev[vc] == NULL) { sc->dev[vc] = sc_alloc_tty(vc, vc + unit * MAXCONS); if (vc == 0 && sc->dev == main_devs) SC_STAT(sc->dev[0]) = &main_console; } /* * The first vty already has struct tty and scr_stat initialized * in scinit(). The other vtys will have these structs when * first opened. */ } dev = make_dev( &consolectl_devsw, 0, UID_ROOT, GID_WHEEL, 0600, "consolectl"); dev->si_drv1 = sc->dev[0]; return 0; } static void scmeminit(void *arg) { if (!vty_enabled(VTY_SC)) return; if (sc_malloc) return; sc_malloc = TRUE; /* * As soon as malloc() becomes functional, we had better allocate * various buffers for the kernel console. */ if (sc_console_unit < 0) /* sc_console == NULL */ return; /* copy the temporary buffer to the final buffer */ sc_alloc_scr_buffer(sc_console, FALSE, FALSE); #ifndef SC_NO_CUTPASTE sc_alloc_cut_buffer(sc_console, FALSE); #endif #ifndef SC_NO_HISTORY /* initialize history buffer & pointers */ sc_alloc_history_buffer(sc_console, 0, 0, FALSE); #endif } /* XXX */ SYSINIT(sc_mem, SI_SUB_KMEM, SI_ORDER_ANY, scmeminit, NULL); static int scdevtounit(struct tty *tp) { int vty = SC_VTY(tp); if (vty == SC_CONSOLECTL) return ((sc_console != NULL) ? sc_console->sc->unit : -1); else if ((vty < 0) || (vty >= MAXCONS * sc_max_unit())) return -1; else return vty / MAXCONS; } static int sctty_open(struct tty *tp) { int unit = scdevtounit(tp); sc_softc_t *sc; scr_stat *scp; keyarg_t key; DPRINTF(5, ("scopen: dev:%s, unit:%d, vty:%d\n", devtoname(tp->t_dev), unit, SC_VTY(tp))); sc = sc_get_softc( unit, (sc_console_unit == unit) ? SC_KERNEL_CONSOLE : 0); if (sc == NULL) return ENXIO; if (!tty_opened(tp)) { /* Use the current setting of the <-- key as default VERASE. */ /* If the Delete key is preferable, an stty is necessary */ if (sc->kbd != NULL) { key.keynum = KEYCODE_BS; (void)kbdd_ioctl(sc->kbd, GIO_KEYMAPENT, (caddr_t)&key); tp->t_termios.c_cc[VERASE] = key.key.map[0]; } } scp = sc_get_stat(tp); if (scp == NULL) { scp = SC_STAT(tp) = alloc_scp(sc, SC_VTY(tp)); if (ISGRAPHSC(scp)) sc_set_pixel_mode(scp, NULL, 0, 0, 16, 8); } if (!tp->t_winsize.ws_col && !tp->t_winsize.ws_row) { tp->t_winsize.ws_col = scp->xsize; tp->t_winsize.ws_row = scp->ysize; } return (0); } static void sctty_close(struct tty *tp) { scr_stat *scp; int s; if (SC_VTY(tp) != SC_CONSOLECTL) { scp = sc_get_stat(tp); /* were we in the middle of the VT switching process? */ DPRINTF(5, ("sc%d: scclose(), ", scp->sc->unit)); s = spltty(); if ((scp == scp->sc->cur_scp) && (scp->sc->unit == sc_console_unit)) cnavailable(sc_consptr, TRUE); if (finish_vt_rel(scp, TRUE, &s) == 0) /* force release */ DPRINTF(5, ("reset WAIT_REL, ")); if (finish_vt_acq(scp) == 0) /* force acknowledge */ DPRINTF(5, ("reset WAIT_ACQ, ")); #ifdef not_yet_done if (scp == &main_console) { scp->pid = 0; scp->proc = NULL; scp->smode.mode = VT_AUTO; } else { sc_vtb_destroy(&scp->vtb); sc_vtb_destroy(&scp->scr); sc_free_history_buffer(scp, scp->ysize); SC_STAT(tp) = NULL; free(scp, M_DEVBUF); } #else scp->pid = 0; scp->proc = NULL; scp->smode.mode = VT_AUTO; #endif scp->kbd_mode = K_XLATE; if (scp == scp->sc->cur_scp) (void)kbdd_ioctl( scp->sc->kbd, KDSKBMODE, (caddr_t)&scp->kbd_mode); DPRINTF(5, ("done.\n")); } } #if 0 /* XXX mpsafetty: fix screensaver. What about outwakeup? */ static int scread(struct cdev *dev, struct uio *uio, int flag) { if (!sc_saver_keyb_only) sc_touch_scrn_saver(); return ttyread(dev, uio, flag); } #endif static int sckbdevent(keyboard_t *thiskbd, int event, void *arg) { sc_softc_t *sc; struct tty *cur_tty; int c, error = 0; size_t len; const u_char *cp; sc = (sc_softc_t *)arg; /* assert(thiskbd == sc->kbd) */ mtx_lock(&Giant); switch (event) { case KBDIO_KEYINPUT: break; case KBDIO_UNLOADING: sc->kbd = NULL; kbd_release(thiskbd, (void *)&sc->kbd); goto done; default: error = EINVAL; goto done; } /* * Loop while there is still input to get from the keyboard. * I don't think this is nessesary, and it doesn't fix * the Xaccel-2.1 keyboard hang, but it can't hurt. XXX */ while ((c = scgetc(sc, SCGETC_NONBLOCK, NULL)) != NOKEY) { cur_tty = SC_DEV(sc, sc->cur_scp->index); if (!tty_opened_ns(cur_tty)) continue; if ((*sc->cur_scp->tsw->te_input)(sc->cur_scp, c, cur_tty)) continue; switch (KEYFLAGS(c)) { case 0x0000: /* normal key */ ttydisc_rint(cur_tty, KEYCHAR(c), 0); break; case FKEY: /* function key, return string */ cp = (*sc->cur_scp->tsw->te_fkeystr)(sc->cur_scp, c); if (cp != NULL) { ttydisc_rint_simple(cur_tty, cp, strlen(cp)); break; } cp = kbdd_get_fkeystr(thiskbd, KEYCHAR(c), &len); if (cp != NULL) ttydisc_rint_simple(cur_tty, cp, len); break; case MKEY: /* meta is active, prepend ESC */ ttydisc_rint(cur_tty, 0x1b, 0); ttydisc_rint(cur_tty, KEYCHAR(c), 0); break; case BKEY: /* backtab fixed sequence (esc [ Z) */ ttydisc_rint_simple(cur_tty, "\x1B[Z", 3); break; } ttydisc_rint_done(cur_tty); } sc->cur_scp->status |= MOUSE_HIDDEN; done: mtx_unlock(&Giant); return (error); } static int sctty_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { int error; int i; struct cursor_attr *cap; sc_softc_t *sc; scr_stat *scp; int s; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; #endif /* If there is a user_ioctl function call that first */ if (sc_user_ioctl) { error = (*sc_user_ioctl)(tp, cmd, data, td); if (error != ENOIOCTL) return error; } error = sc_vid_ioctl(tp, cmd, data, td); if (error != ENOIOCTL) return error; #ifndef SC_NO_HISTORY error = sc_hist_ioctl(tp, cmd, data, td); if (error != ENOIOCTL) return error; #endif #ifndef SC_NO_SYSMOUSE error = sc_mouse_ioctl(tp, cmd, data, td); if (error != ENOIOCTL) return error; #endif scp = sc_get_stat(tp); /* assert(scp != NULL) */ /* scp is sc_console, if SC_VTY(dev) == SC_CONSOLECTL. */ sc = scp->sc; if (scp->tsw) { error = (*scp->tsw->te_ioctl)(scp, tp, cmd, data, td); if (error != ENOIOCTL) return error; } switch (cmd) { /* process console hardware related ioctl's */ case GIO_ATTR: /* get current attributes */ /* this ioctl is not processed here, but in the terminal * emulator */ return ENOTTY; case GIO_COLOR: /* is this a color console ? */ *(int *)data = (sc->adp->va_flags & V_ADP_COLOR) ? 1 : 0; return 0; case CONS_BLANKTIME: /* set screen saver timeout (0 = no saver) */ if (*(int *)data < 0 || *(int *)data > MAX_BLANKTIME) return EINVAL; s = spltty(); scrn_blank_time = *(int *)data; run_scrn_saver = (scrn_blank_time != 0); splx(s); return 0; case CONS_CURSORTYPE: /* set cursor type (old interface + HIDDEN) */ s = spltty(); *(int *)data &= CONS_CURSOR_ATTRS; sc_change_cursor_shape(scp, *(int *)data, -1, -1); splx(s); return 0; case CONS_GETCURSORSHAPE: /* get cursor shape (new interface) */ switch (((int *)data)[0] & (CONS_DEFAULT_CURSOR | CONS_LOCAL_CURSOR)) { case 0: cap = &sc->curs_attr; break; case CONS_LOCAL_CURSOR: cap = &scp->base_curs_attr; break; case CONS_DEFAULT_CURSOR: cap = &sc->dflt_curs_attr; break; case CONS_DEFAULT_CURSOR | CONS_LOCAL_CURSOR: cap = &scp->dflt_curs_attr; break; } if (((int *)data)[0] & CONS_CHARCURSOR_COLORS) { ((int *)data)[1] = cap->bg[0]; ((int *)data)[2] = cap->bg[1]; } else if (((int *)data)[0] & CONS_MOUSECURSOR_COLORS) { ((int *)data)[1] = cap->mouse_ba; ((int *)data)[2] = cap->mouse_ia; } else { ((int *)data)[1] = cap->base; ((int *)data)[2] = cap->height; } ((int *)data)[0] = cap->flags; return 0; case CONS_SETCURSORSHAPE: /* set cursor shape (new interface) */ s = spltty(); sc_change_cursor_shape( scp, ((int *)data)[0], ((int *)data)[1], ((int *)data)[2]); splx(s); return 0; case CONS_BELLTYPE: /* set bell type sound/visual */ if ((*(int *)data) & CONS_VISUAL_BELL) sc->flags |= SC_VISUAL_BELL; else sc->flags &= ~SC_VISUAL_BELL; if ((*(int *)data) & CONS_QUIET_BELL) sc->flags |= SC_QUIET_BELL; else sc->flags &= ~SC_QUIET_BELL; return 0; case CONS_GETINFO: /* get current (virtual) console info */ { vid_info_t *ptr = (vid_info_t *)data; if (ptr->size == sizeof(struct vid_info)) { ptr->m_num = sc->cur_scp->index; ptr->font_size = scp->font_size; ptr->mv_col = scp->xpos; ptr->mv_row = scp->ypos; ptr->mv_csz = scp->xsize; ptr->mv_rsz = scp->ysize; ptr->mv_hsz = (scp->history != NULL) ? scp->history->vtb_rows : 0; /* * The following fields are filled by the terminal * emulator. XXX * * ptr->mv_norm.fore * ptr->mv_norm.back * ptr->mv_rev.fore * ptr->mv_rev.back */ ptr->mv_grfc.fore = 0; /* not supported */ ptr->mv_grfc.back = 0; /* not supported */ ptr->mv_ovscan = scp->border; if (scp == sc->cur_scp) save_kbd_state(scp); ptr->mk_keylock = scp->status & LOCK_MASK; return 0; } return EINVAL; } case CONS_GETVERS: /* get version number */ *(int *)data = 0x200; /* version 2.0 */ return 0; case CONS_IDLE: /* see if the screen has been idle */ /* * When the screen is in the GRAPHICS_MODE or UNKNOWN_MODE, * the user process may have been writing something on the * screen and syscons is not aware of it. Declare the screen * is NOT idle if it is in one of these modes. But there is * an exception to it; if a screen saver is running in the * graphics mode in the current screen, we should say that the * screen has been idle. */ *(int *)data = (sc->flags & SC_SCRN_IDLE) && (!ISGRAPHSC(sc->cur_scp) || (sc->cur_scp->status & SAVER_RUNNING)); return 0; case CONS_SAVERMODE: /* set saver mode */ switch (*(int *)data) { case CONS_NO_SAVER: case CONS_USR_SAVER: /* if a LKM screen saver is running, stop it first. */ scsplash_stick(FALSE); saver_mode = *(int *)data; s = spltty(); #ifdef DEV_SPLASH if ((error = wait_scrn_saver_stop(NULL))) { splx(s); return error; } #endif run_scrn_saver = TRUE; if (saver_mode == CONS_USR_SAVER) scp->status |= SAVER_RUNNING; else scp->status &= ~SAVER_RUNNING; scsplash_stick(TRUE); splx(s); break; case CONS_LKM_SAVER: s = spltty(); if ((saver_mode == CONS_USR_SAVER) && (scp->status & SAVER_RUNNING)) scp->status &= ~SAVER_RUNNING; saver_mode = *(int *)data; splx(s); break; default: return EINVAL; } return 0; case CONS_SAVERSTART: /* immediately start/stop the screen saver */ /* * Note that this ioctl does not guarantee the screen saver * actually starts or stops. It merely attempts to do so... */ s = spltty(); run_scrn_saver = (*(int *)data != 0); if (run_scrn_saver) sc->scrn_time_stamp -= scrn_blank_time; splx(s); return 0; case CONS_SCRSHOT: /* get a screen shot */ { int retval, hist_rsz; size_t lsize, csize; vm_offset_t frbp, hstp; unsigned lnum; scrshot_t *ptr = (scrshot_t *)data; void *outp = ptr->buf; if (ptr->x < 0 || ptr->y < 0 || ptr->xsize < 0 || ptr->ysize < 0) return EINVAL; s = spltty(); if (ISGRAPHSC(scp)) { splx(s); return EOPNOTSUPP; } hist_rsz = (scp->history != NULL) ? scp->history->vtb_rows : 0; if (((u_int)ptr->x + ptr->xsize) > scp->xsize || ((u_int)ptr->y + ptr->ysize) > (scp->ysize + hist_rsz)) { splx(s); return EINVAL; } lsize = scp->xsize * sizeof(u_int16_t); csize = ptr->xsize * sizeof(u_int16_t); /* Pointer to the last line of framebuffer */ frbp = scp->vtb.vtb_buffer + scp->ysize * lsize + ptr->x * sizeof(u_int16_t); /* Pointer to the last line of target buffer */ outp = (char *)outp + ptr->ysize * csize; /* Pointer to the last line of history buffer */ if (scp->history != NULL) hstp = scp->history->vtb_buffer + sc_vtb_tail(scp->history) * sizeof(u_int16_t) + ptr->x * sizeof(u_int16_t); else hstp = 0; retval = 0; for (lnum = 0; lnum < (ptr->y + ptr->ysize); lnum++) { if (lnum < scp->ysize) { frbp -= lsize; } else { hstp -= lsize; if (hstp < scp->history->vtb_buffer) hstp += scp->history->vtb_rows * lsize; frbp = hstp; } if (lnum < ptr->y) continue; outp = (char *)outp - csize; retval = copyout((void *)frbp, outp, csize); if (retval != 0) break; } splx(s); return retval; } case VT_SETMODE: /* set screen switcher mode */ { struct vt_mode *mode; struct proc *p1; mode = (struct vt_mode *)data; DPRINTF(5, ("%s%d: VT_SETMODE ", SC_DRIVER_NAME, sc->unit)); if (scp->smode.mode == VT_PROCESS) { p1 = pfind(scp->pid); if (scp->proc == p1 && scp->proc != td->td_proc) { if (p1) PROC_UNLOCK(p1); DPRINTF(5, ("error EPERM\n")); return EPERM; } if (p1) PROC_UNLOCK(p1); } s = spltty(); if (mode->mode == VT_AUTO) { scp->smode.mode = VT_AUTO; scp->proc = NULL; scp->pid = 0; DPRINTF(5, ("VT_AUTO, ")); if ((scp == sc->cur_scp) && (sc->unit == sc_console_unit)) cnavailable(sc_consptr, TRUE); /* were we in the middle of the vty switching process? */ if (finish_vt_rel(scp, TRUE, &s) == 0) DPRINTF(5, ("reset WAIT_REL, ")); if (finish_vt_acq(scp) == 0) DPRINTF(5, ("reset WAIT_ACQ, ")); } else { if (!ISSIGVALID(mode->relsig) || !ISSIGVALID(mode->acqsig) || !ISSIGVALID(mode->frsig)) { splx(s); DPRINTF(5, ("error EINVAL\n")); return EINVAL; } DPRINTF(5, ("VT_PROCESS %d, ", td->td_proc->p_pid)); bcopy(data, &scp->smode, sizeof(struct vt_mode)); scp->proc = td->td_proc; scp->pid = scp->proc->p_pid; if ((scp == sc->cur_scp) && (sc->unit == sc_console_unit)) cnavailable(sc_consptr, FALSE); } splx(s); DPRINTF(5, ("\n")); return 0; } case VT_GETMODE: /* get screen switcher mode */ bcopy(&scp->smode, data, sizeof(struct vt_mode)); return 0; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('v', 4): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case VT_RELDISP: /* screen switcher ioctl */ s = spltty(); /* * This must be the current vty which is in the VT_PROCESS * switching mode... */ if ((scp != sc->cur_scp) || (scp->smode.mode != VT_PROCESS)) { splx(s); return EINVAL; } /* ...and this process is controlling it. */ if (scp->proc != td->td_proc) { splx(s); return EPERM; } error = EINVAL; switch (*(int *)data) { case VT_FALSE: /* user refuses to release screen, abort */ if ((error = finish_vt_rel(scp, FALSE, &s)) == 0) DPRINTF(5, ("%s%d: VT_FALSE\n", SC_DRIVER_NAME, sc->unit)); break; case VT_TRUE: /* user has released screen, go on */ if ((error = finish_vt_rel(scp, TRUE, &s)) == 0) DPRINTF(5, ("%s%d: VT_TRUE\n", SC_DRIVER_NAME, sc->unit)); break; case VT_ACKACQ: /* acquire acknowledged, switch completed */ if ((error = finish_vt_acq(scp)) == 0) DPRINTF(5, ("%s%d: VT_ACKACQ\n", SC_DRIVER_NAME, sc->unit)); break; default: break; } splx(s); return error; case VT_OPENQRY: /* return free virtual console */ for (i = sc->first_vty; i < sc->first_vty + sc->vtys; i++) { tp = SC_DEV(sc, i); if (!tty_opened_ns(tp)) { *(int *)data = i + 1; return 0; } } return EINVAL; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('v', 5): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case VT_ACTIVATE: /* switch to screen *data */ i = (*(int *)data == 0) ? scp->index : (*(int *)data - 1); s = spltty(); error = sc_clean_up(sc->cur_scp); splx(s); if (error) return error; error = sc_switch_scr(sc, i); return (error); #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('v', 6): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case VT_WAITACTIVE: /* wait for switch to occur */ i = (*(int *)data == 0) ? scp->index : (*(int *)data - 1); if ((i < sc->first_vty) || (i >= sc->first_vty + sc->vtys)) return EINVAL; if (i == sc->cur_scp->index) return 0; error = tsleep(VTY_WCHAN(sc, i), (PZERO + 1) | PCATCH, "waitvt", 0); return error; case VT_GETACTIVE: /* get active vty # */ *(int *)data = sc->cur_scp->index + 1; return 0; case VT_GETINDEX: /* get this vty # */ *(int *)data = scp->index + 1; return 0; case VT_LOCKSWITCH: /* prevent vty switching */ if ((*(int *)data) & 0x01) sc->flags |= SC_SCRN_VTYLOCK; else sc->flags &= ~SC_SCRN_VTYLOCK; return 0; case KDENABIO: /* allow io operations */ error = priv_check(td, PRIV_IO); if (error != 0) return error; error = securelevel_gt(td->td_ucred, 0); if (error != 0) return error; #ifdef __i386__ td->td_frame->tf_eflags |= PSL_IOPL; #elif defined(__amd64__) td->td_frame->tf_rflags |= PSL_IOPL; #endif return 0; case KDDISABIO: /* disallow io operations (default) */ #ifdef __i386__ td->td_frame->tf_eflags &= ~PSL_IOPL; #elif defined(__amd64__) td->td_frame->tf_rflags &= ~PSL_IOPL; #endif return 0; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 20): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBSTATE: /* set keyboard state (locks) */ if (*(int *)data & ~LOCK_MASK) return EINVAL; scp->status &= ~LOCK_MASK; scp->status |= *(int *)data; if (scp == sc->cur_scp) update_kbd_state(scp, scp->status, LOCK_MASK); return 0; case KDGKBSTATE: /* get keyboard state (locks) */ if (scp == sc->cur_scp) save_kbd_state(scp); *(int *)data = scp->status & LOCK_MASK; return 0; case KDGETREPEAT: /* get keyboard repeat & delay rates */ case KDSETREPEAT: /* set keyboard repeat & delay rates (new) */ error = kbdd_ioctl(sc->kbd, cmd, data); if (error == ENOIOCTL) error = ENODEV; return error; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 67): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETRAD: /* set keyboard repeat & delay rates (old) */ if (*(int *)data & ~0x7f) return EINVAL; error = kbdd_ioctl(sc->kbd, KDSETRAD, data); if (error == ENOIOCTL) error = ENODEV; return error; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 7): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBMODE: /* set keyboard mode */ switch (*(int *)data) { case K_XLATE: /* switch to XLT ascii mode */ case K_RAW: /* switch to RAW scancode mode */ case K_CODE: /* switch to CODE mode */ scp->kbd_mode = *(int *)data; if (scp == sc->cur_scp) (void)kbdd_ioctl(sc->kbd, KDSKBMODE, data); return 0; default: return EINVAL; } /* NOT REACHED */ case KDGKBMODE: /* get keyboard mode */ *(int *)data = scp->kbd_mode; return 0; case KDGKBINFO: error = kbdd_ioctl(sc->kbd, cmd, data); if (error == ENOIOCTL) error = ENODEV; return error; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 8): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDMKTONE: /* sound the bell */ if (*(int *)data) sc_bell(scp, (*(int *)data) & 0xffff, (((*(int *)data) >> 16) & 0xffff) * hz / 1000); else sc_bell(scp, scp->bell_pitch, scp->bell_duration); return 0; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 63): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KIOCSOUND: /* make tone (*data) hz */ if (scp == sc->cur_scp) { if (*(int *)data) return sc_tone(*(int *)data); else return sc_tone(0); } return 0; case KDGKBTYPE: /* get keyboard type */ error = kbdd_ioctl(sc->kbd, cmd, data); if (error == ENOIOCTL) { /* always return something? XXX */ *(int *)data = 0; } return 0; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 66): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETLED: /* set keyboard LED status */ if (*(int *)data & ~LED_MASK) /* FIXME: LOCK_MASK? */ return EINVAL; scp->status &= ~LED_MASK; scp->status |= *(int *)data; if (scp == sc->cur_scp) update_kbd_leds(scp, scp->status); return 0; case KDGETLED: /* get keyboard LED status */ if (scp == sc->cur_scp) save_kbd_state(scp); *(int *)data = scp->status & LED_MASK; return 0; case KBADDKBD: /* add/remove keyboard to/from mux */ case KBRELKBD: error = kbdd_ioctl(sc->kbd, cmd, data); if (error == ENOIOCTL) error = ENODEV; return error; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('c', 110): ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; /* FALLTHROUGH */ #endif case CONS_SETKBD: /* set the new keyboard */ { keyboard_t *newkbd; s = spltty(); newkbd = kbd_get_keyboard(*(int *)data); if (newkbd == NULL) { splx(s); return EINVAL; } error = 0; if (sc->kbd != newkbd) { i = kbd_allocate(newkbd->kb_name, newkbd->kb_unit, (void *)&sc->kbd, sckbdevent, sc); /* i == newkbd->kb_index */ if (i >= 0) { if (sc->kbd != NULL) { save_kbd_state(sc->cur_scp); kbd_release( sc->kbd, (void *)&sc->kbd); } sc->kbd = kbd_get_keyboard(i); /* sc->kbd == newkbd */ (void)kbdd_ioctl(sc->kbd, KDSKBMODE, (caddr_t)&sc->cur_scp->kbd_mode); update_kbd_state(sc->cur_scp, sc->cur_scp->status, LOCK_MASK); } else { error = EPERM; /* XXX */ } } splx(s); return error; } case CONS_RELKBD: /* release the current keyboard */ s = spltty(); error = 0; if (sc->kbd != NULL) { save_kbd_state(sc->cur_scp); error = kbd_release(sc->kbd, (void *)&sc->kbd); if (error == 0) sc->kbd = NULL; } splx(s); return error; case CONS_GETTERM: /* get the current terminal emulator info */ { sc_term_sw_t *sw; if (((term_info_t *)data)->ti_index == 0) { sw = scp->tsw; } else { sw = sc_term_match_by_number( ((term_info_t *)data)->ti_index); } if (sw != NULL) { strncpy(((term_info_t *)data)->ti_name, sw->te_name, sizeof(((term_info_t *)data)->ti_name)); strncpy(((term_info_t *)data)->ti_desc, sw->te_desc, sizeof(((term_info_t *)data)->ti_desc)); ((term_info_t *)data)->ti_flags = 0; return 0; } else { ((term_info_t *)data)->ti_name[0] = '\0'; ((term_info_t *)data)->ti_desc[0] = '\0'; ((term_info_t *)data)->ti_flags = 0; return EINVAL; } } case CONS_SETTERM: /* set the current terminal emulator */ s = spltty(); error = sc_init_emulator(scp, ((term_info_t *)data)->ti_name); /* FIXME: what if scp == sc_console! XXX */ splx(s); return error; case GIO_SCRNMAP: /* get output translation table */ bcopy(&sc->scr_map, data, sizeof(sc->scr_map)); return 0; case PIO_SCRNMAP: /* set output translation table */ bcopy(data, &sc->scr_map, sizeof(sc->scr_map)); for (i = 0; i < sizeof(sc->scr_map); i++) { sc->scr_rmap[sc->scr_map[i]] = i; } return 0; case GIO_KEYMAP: /* get keyboard translation table */ case PIO_KEYMAP: /* set keyboard translation table */ case OGIO_KEYMAP: /* get keyboard translation table (compat) */ case OPIO_KEYMAP: /* set keyboard translation table (compat) */ case GIO_DEADKEYMAP: /* get accent key translation table */ case PIO_DEADKEYMAP: /* set accent key translation table */ case GETFKEY: /* get function key string */ case SETFKEY: /* set function key string */ error = kbdd_ioctl(sc->kbd, cmd, data); if (error == ENOIOCTL) error = ENODEV; return error; #ifndef SC_NO_FONT_LOADING case PIO_FONT8x8: /* set 8x8 dot font */ if (!ISFONTAVAIL(sc->adp->va_flags)) return ENXIO; bcopy(data, sc->font_8, 8 * 256); sc->fonts_loaded |= FONT_8; /* * FONT KLUDGE * Always use the font page #0. XXX * Don't load if the current font size is not 8x8. */ if (ISTEXTSC(sc->cur_scp) && (sc->cur_scp->font_size < 14)) sc_load_font(sc->cur_scp, 0, 8, 8, sc->font_8, 0, 256); return 0; case GIO_FONT8x8: /* get 8x8 dot font */ if (!ISFONTAVAIL(sc->adp->va_flags)) return ENXIO; if (sc->fonts_loaded & FONT_8) { bcopy(sc->font_8, data, 8 * 256); return 0; } else return ENXIO; case PIO_FONT8x14: /* set 8x14 dot font */ if (!ISFONTAVAIL(sc->adp->va_flags)) return ENXIO; bcopy(data, sc->font_14, 14 * 256); sc->fonts_loaded |= FONT_14; /* * FONT KLUDGE * Always use the font page #0. XXX * Don't load if the current font size is not 8x14. */ if (ISTEXTSC(sc->cur_scp) && (sc->cur_scp->font_size >= 14) && (sc->cur_scp->font_size < 16)) sc_load_font( sc->cur_scp, 0, 14, 8, sc->font_14, 0, 256); return 0; case GIO_FONT8x14: /* get 8x14 dot font */ if (!ISFONTAVAIL(sc->adp->va_flags)) return ENXIO; if (sc->fonts_loaded & FONT_14) { bcopy(sc->font_14, data, 14 * 256); return 0; } else return ENXIO; case PIO_FONT8x16: /* set 8x16 dot font */ if (!ISFONTAVAIL(sc->adp->va_flags)) return ENXIO; bcopy(data, sc->font_16, 16 * 256); sc->fonts_loaded |= FONT_16; /* * FONT KLUDGE * Always use the font page #0. XXX * Don't load if the current font size is not 8x16. */ if (ISTEXTSC(sc->cur_scp) && (sc->cur_scp->font_size >= 16)) sc_load_font( sc->cur_scp, 0, 16, 8, sc->font_16, 0, 256); return 0; case GIO_FONT8x16: /* get 8x16 dot font */ if (!ISFONTAVAIL(sc->adp->va_flags)) return ENXIO; if (sc->fonts_loaded & FONT_16) { bcopy(sc->font_16, data, 16 * 256); return 0; } else return ENXIO; #endif /* SC_NO_FONT_LOADING */ default: break; } return (ENOIOCTL); } static int consolectl_ioctl( struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { return sctty_ioctl(dev->si_drv1, cmd, data, td); } static int consolectl_close(struct cdev *dev, int flags, int mode, struct thread *td) { #ifndef SC_NO_SYSMOUSE mouse_info_t info; memset(&info, 0, sizeof(info)); info.operation = MOUSE_ACTION; /* * Make sure all buttons are released when moused and other * console daemons exit, so that no buttons are left pressed. */ (void)sctty_ioctl(dev->si_drv1, CONS_MOUSECTL, (caddr_t)&info, td); #endif return (0); } static void sc_cnprobe(struct consdev *cp) { int unit; int flags; if (!vty_enabled(VTY_SC)) { cp->cn_pri = CN_DEAD; return; } cp->cn_pri = sc_get_cons_priority(&unit, &flags); /* a video card is always required */ if (!scvidprobe(unit, flags, TRUE)) cp->cn_pri = CN_DEAD; /* syscons will become console even when there is no keyboard */ sckbdprobe(unit, flags, TRUE); if (cp->cn_pri == CN_DEAD) return; /* initialize required fields */ strcpy(cp->cn_name, "ttyv0"); } static void sc_cninit(struct consdev *cp) { int unit; int flags; sc_get_cons_priority(&unit, &flags); scinit(unit, flags | SC_KERNEL_CONSOLE); sc_console_unit = unit; sc_console = sc_get_stat(sc_get_softc(unit, SC_KERNEL_CONSOLE)->dev[0]); sc_consptr = cp; } static void sc_cnterm(struct consdev *cp) { void *ts; int i; /* we are not the kernel console any more, release everything */ if (sc_console_unit < 0) return; /* shouldn't happen */ #if 0 /* XXX */ sc_clear_screen(sc_console); sccnupdate(sc_console); #endif if (sc_ktsw != NULL) { for (i = 0; i <= mp_maxid; i++) { ts = sc_kts[i]; sc_kts[i] = NULL; (*sc_ktsw->te_term)(sc_console, &ts); free(ts, M_DEVBUF); } sc_ktsw = NULL; } scterm(sc_console_unit, SC_KERNEL_CONSOLE); sc_console_unit = -1; sc_console = NULL; } static void sccnclose(sc_softc_t *sc, struct sc_cnstate *sp); static int sc_cngetc_locked(struct sc_cnstate *sp); static void sccnkbdlock(sc_softc_t *sc, struct sc_cnstate *sp); static void sccnkbdunlock(sc_softc_t *sc, struct sc_cnstate *sp); static void sccnopen(sc_softc_t *sc, struct sc_cnstate *sp, int flags); static void sccnscrlock(sc_softc_t *sc, struct sc_cnstate *sp); static void sccnscrunlock(sc_softc_t *sc, struct sc_cnstate *sp); static void sccnkbdlock(sc_softc_t *sc, struct sc_cnstate *sp) { /* * Locking method: hope for the best. * The keyboard is supposed to be Giant locked. We can't handle that * in general. The kdb_active case here is not safe, and we will * proceed without the lock in all cases. */ sp->kbd_locked = !kdb_active && mtx_trylock(&Giant); } static void sccnkbdunlock(sc_softc_t *sc, struct sc_cnstate *sp) { if (sp->kbd_locked) mtx_unlock(&Giant); sp->kbd_locked = FALSE; } static void sccnscrlock(sc_softc_t *sc, struct sc_cnstate *sp) { int retries; /** * Locking method: * - if kdb_active and video_mtx is not owned by anyone, then lock * by kdb remaining active * - if !kdb_active, try to acquire video_mtx without blocking or * recursing; if we get it then it works normally. * Note that video_mtx is especially unusable if we already own it, * since then it is protecting something and syscons is not reentrant * enough to ignore the protection even in the kdb_active case. */ if (kdb_active) { sp->kdb_locked = sc->video_mtx.mtx_lock == MTX_UNOWNED || SCHEDULER_STOPPED(); sp->mtx_locked = FALSE; } else { sp->kdb_locked = FALSE; for (retries = 0; retries < 1000; retries++) { sp->mtx_locked = mtx_trylock_spin_flags( &sc->video_mtx, MTX_QUIET) != 0; if (SCHEDULER_STOPPED()) { sp->kdb_locked = TRUE; sp->mtx_locked = FALSE; break; } if (sp->mtx_locked) break; DELAY(1); } } } static void sccnscrunlock(sc_softc_t *sc, struct sc_cnstate *sp) { if (sp->mtx_locked) mtx_unlock_spin(&sc->video_mtx); sp->mtx_locked = sp->kdb_locked = FALSE; } static void sccnopen(sc_softc_t *sc, struct sc_cnstate *sp, int flags) { int kbd_mode; /* assert(sc_console_unit >= 0) */ sp->kbd_opened = FALSE; sp->scr_opened = FALSE; sp->kbd_locked = FALSE; /* Opening the keyboard is optional. */ if (!(flags & 1) || sc->kbd == NULL) goto over_keyboard; sccnkbdlock(sc, sp); /* * Make sure the keyboard is accessible even when the kbd device * driver is disabled. */ kbdd_enable(sc->kbd); /* Switch the keyboard to console mode (K_XLATE, polled) on all scp's. */ kbd_mode = K_XLATE; (void)kbdd_ioctl(sc->kbd, KDSKBMODE, (caddr_t)&kbd_mode); sc->kbd_open_level++; kbdd_poll(sc->kbd, TRUE); sp->kbd_opened = TRUE; over_keyboard:; /* The screen is opened iff locking it succeeds. */ sccnscrlock(sc, sp); if (!sp->kdb_locked && !sp->mtx_locked) return; sp->scr_opened = TRUE; /* The screen switch is optional. */ if (!(flags & 2)) return; /* try to switch to the kernel console screen */ if (!cold && sc->cur_scp->index != sc_console->index && sc->cur_scp->smode.mode == VT_AUTO && sc_console->smode.mode == VT_AUTO) sc_switch_scr(sc, sc_console->index); } static void sccnclose(sc_softc_t *sc, struct sc_cnstate *sp) { sp->scr_opened = FALSE; sccnscrunlock(sc, sp); if (!sp->kbd_opened) return; /* Restore keyboard mode (for the current, possibly-changed scp). */ kbdd_poll(sc->kbd, FALSE); if (--sc->kbd_open_level == 0) (void)kbdd_ioctl( sc->kbd, KDSKBMODE, (caddr_t)&sc->cur_scp->kbd_mode); kbdd_disable(sc->kbd); sp->kbd_opened = FALSE; sccnkbdunlock(sc, sp); } /* * Grabbing switches the screen and keyboard focus to sc_console and the * keyboard mode to (K_XLATE, polled). Only switching to polled mode is * essential (for preventing the interrupt handler from eating input * between polls). Focus is part of the UI, and the other switches are * work just was well when they are done on every entry and exit. * * Screen switches while grabbed are supported, and to maintain focus for * this ungrabbing and closing only restore the polling state and then * the keyboard mode if on the original screen. */ static void sc_cngrab(struct consdev *cp) { sc_softc_t *sc; int lev; sc = sc_console->sc; lev = atomic_fetchadd_int(&sc->grab_level, 1); if (lev >= 0 && lev < 2) { sccnopen(sc, &sc->grab_state[lev], 1 | 2); sccnscrunlock(sc, &sc->grab_state[lev]); sccnkbdunlock(sc, &sc->grab_state[lev]); } } static void sc_cnungrab(struct consdev *cp) { sc_softc_t *sc; int lev; sc = sc_console->sc; lev = atomic_load_acq_int(&sc->grab_level) - 1; if (lev >= 0 && lev < 2) { sccnkbdlock(sc, &sc->grab_state[lev]); sccnscrlock(sc, &sc->grab_state[lev]); sccnclose(sc, &sc->grab_state[lev]); } atomic_add_int(&sc->grab_level, -1); } static char sc_cnputc_log[0x1000]; static u_int sc_cnputc_loghead; static u_int sc_cnputc_logtail; static void sc_cnputc(struct consdev *cd, int c) { struct sc_cnstate st; u_char buf[1]; scr_stat *scp = sc_console; void *oldts, *ts; struct sc_term_sw *oldtsw; #ifndef SC_NO_HISTORY #if 0 struct tty *tp; #endif #endif /* !SC_NO_HISTORY */ u_int head; int s; /* assert(sc_console != NULL) */ sccnopen(scp->sc, &st, 0); /* * Log the output. * * In the unlocked case, the logging is intentionally only * perfectly atomic for the indexes. */ head = atomic_fetchadd_int(&sc_cnputc_loghead, 1); sc_cnputc_log[head % sizeof(sc_cnputc_log)] = c; /* * If we couldn't open, do special reentrant output and return to defer * normal output. */ if (!st.scr_opened) { ec_putc(c); return; } #ifndef SC_NO_HISTORY if (scp == scp->sc->cur_scp && scp->status & SLKED) { scp->status &= ~SLKED; update_kbd_state(scp, scp->status, SLKED); if (scp->status & BUFFER_SAVED) { if (!sc_hist_restore(scp)) sc_remove_cutmarking(scp); scp->status &= ~BUFFER_SAVED; scp->status |= CURSOR_ENABLED; sc_draw_cursor_image(scp); } #if 0 /* * XXX: Now that TTY's have their own locks, we cannot process * any data after disabling scroll lock. cnputs already holds a * spinlock. */ tp = SC_DEV(scp->sc, scp->index); /* XXX "tp" can be NULL */ tty_lock(tp); if (tty_opened(tp)) sctty_outwakeup(tp); tty_unlock(tp); #endif } #endif /* !SC_NO_HISTORY */ /* Play any output still in the log (our char may already be done). */ while (sc_cnputc_logtail != atomic_load_acq_int(&sc_cnputc_loghead)) { buf[0] = sc_cnputc_log[sc_cnputc_logtail++ % sizeof(sc_cnputc_log)]; if (atomic_load_acq_int(&sc_cnputc_loghead) - sc_cnputc_logtail >= sizeof(sc_cnputc_log)) continue; /* Console output has a per-CPU "input" state. Switch for it. */ ts = sc_kts[curcpu]; if (ts != NULL) { oldtsw = scp->tsw; oldts = scp->ts; scp->tsw = sc_ktsw; scp->ts = ts; (*scp->tsw->te_sync)(scp); } else { /* Only 1 tsw early. Switch only its attr. */ (*scp->tsw->te_default_attr)( scp, sc_kattrtab[curcpu], SC_KERNEL_CONS_REV_ATTR); } sc_puts(scp, buf, 1); if (ts != NULL) { scp->tsw = oldtsw; scp->ts = oldts; (*scp->tsw->te_sync)(scp); } else { (*scp->tsw->te_default_attr)( scp, SC_KERNEL_CONS_ATTR, SC_KERNEL_CONS_REV_ATTR); } } s = spltty(); /* block sckbdevent and scrn_timer */ sccnupdate(scp); splx(s); sccnclose(scp->sc, &st); } static int sc_cngetc(struct consdev *cd) { struct sc_cnstate st; int c, s; /* assert(sc_console != NULL) */ sccnopen(sc_console->sc, &st, 1); s = spltty(); /* block sckbdevent and scrn_timer while we poll */ if (!st.kbd_opened) { splx(s); sccnclose(sc_console->sc, &st); return -1; /* means no keyboard since we fudged the locking */ } c = sc_cngetc_locked(&st); splx(s); sccnclose(sc_console->sc, &st); return c; } static int sc_cngetc_locked(struct sc_cnstate *sp) { static struct fkeytab fkey; static int fkeycp; scr_stat *scp; const u_char *p; int c; /* * Stop the screen saver and update the screen if necessary. * What if we have been running in the screen saver code... XXX */ if (sp->scr_opened) sc_touch_scrn_saver(); scp = sc_console->sc->cur_scp; /* XXX */ if (sp->scr_opened) sccnupdate(scp); if (fkeycp < fkey.len) return fkey.str[fkeycp++]; c = scgetc(scp->sc, SCGETC_CN | SCGETC_NONBLOCK, sp); switch (KEYFLAGS(c)) { case 0: /* normal char */ return KEYCHAR(c); case FKEY: /* function key */ p = (*scp->tsw->te_fkeystr)(scp, c); if (p != NULL) { fkey.len = strlen(p); bcopy(p, fkey.str, fkey.len); fkeycp = 1; return fkey.str[0]; } p = kbdd_get_fkeystr( scp->sc->kbd, KEYCHAR(c), (size_t *)&fkeycp); fkey.len = fkeycp; if ((p != NULL) && (fkey.len > 0)) { bcopy(p, fkey.str, fkey.len); fkeycp = 1; return fkey.str[0]; } return c; /* XXX */ case NOKEY: case ERRKEY: default: return -1; } /* NOT REACHED */ } static void sccnupdate(scr_stat *scp) { /* this is a cut-down version of scrn_timer()... */ if (suspend_in_progress || scp->sc->font_loading_in_progress) return; if (kdb_active || KERNEL_PANICKED() || shutdown_in_progress) { sc_touch_scrn_saver(); } else if (scp != scp->sc->cur_scp) { return; } if (!run_scrn_saver) scp->sc->flags &= ~SC_SCRN_IDLE; #ifdef DEV_SPLASH if ((saver_mode != CONS_LKM_SAVER) || !(scp->sc->flags & SC_SCRN_IDLE)) if (scp->sc->flags & SC_SCRN_BLANKED) stop_scrn_saver(scp->sc, current_saver); #endif if (scp != scp->sc->cur_scp || scp->sc->blink_in_progress || scp->sc->switch_in_progress) return; /* * FIXME: unlike scrn_timer(), we call scrn_update() from here even * when write_in_progress is non-zero. XXX */ if (!ISGRAPHSC(scp) && !(scp->sc->flags & SC_SCRN_BLANKED)) scrn_update(scp, TRUE); } static void scrn_timer(void *arg) { static time_t kbd_time_stamp = 0; sc_softc_t *sc; scr_stat *scp; int again, rate; int kbdidx; again = (arg != NULL); if (arg != NULL) sc = (sc_softc_t *)arg; else if (sc_console != NULL) sc = sc_console->sc; else return; /* find the vty to update */ scp = sc->cur_scp; /* don't do anything when we are performing some I/O operations */ if (suspend_in_progress || sc->font_loading_in_progress) goto done; if ((sc->kbd == NULL) && (sc->config & SC_AUTODETECT_KBD)) { /* try to allocate a keyboard automatically */ if (kbd_time_stamp != time_uptime) { kbd_time_stamp = time_uptime; kbdidx = sc_allocate_keyboard(sc, -1); if (kbdidx >= 0) { sc->kbd = kbd_get_keyboard(kbdidx); (void)kbdd_ioctl(sc->kbd, KDSKBMODE, (caddr_t)&sc->cur_scp->kbd_mode); update_kbd_state(sc->cur_scp, sc->cur_scp->status, LOCK_MASK); } } } /* should we stop the screen saver? */ if (kdb_active || KERNEL_PANICKED() || shutdown_in_progress) sc_touch_scrn_saver(); if (run_scrn_saver) { if (time_uptime > sc->scrn_time_stamp + scrn_blank_time) sc->flags |= SC_SCRN_IDLE; else sc->flags &= ~SC_SCRN_IDLE; } else { sc->scrn_time_stamp = time_uptime; sc->flags &= ~SC_SCRN_IDLE; if (scrn_blank_time > 0) run_scrn_saver = TRUE; } #ifdef DEV_SPLASH if ((saver_mode != CONS_LKM_SAVER) || !(sc->flags & SC_SCRN_IDLE)) if (sc->flags & SC_SCRN_BLANKED) stop_scrn_saver(sc, current_saver); #endif /* should we just return ? */ if (sc->blink_in_progress || sc->switch_in_progress || sc->write_in_progress) goto done; /* Update the screen */ scp = sc->cur_scp; /* cur_scp may have changed... */ if (!ISGRAPHSC(scp) && !(sc->flags & SC_SCRN_BLANKED)) scrn_update(scp, TRUE); #ifdef DEV_SPLASH /* should we activate the screen saver? */ if ((saver_mode == CONS_LKM_SAVER) && (sc->flags & SC_SCRN_IDLE)) if (!ISGRAPHSC(scp) || (sc->flags & SC_SCRN_BLANKED)) (*current_saver)(sc, TRUE); #endif done: if (again) { /* * Use reduced "refresh" rate if we are in graphics and that is * not a graphical screen saver. In such case we just have * nothing to do. */ if (ISGRAPHSC(scp) && !(sc->flags & SC_SCRN_BLANKED)) rate = 2; else rate = 30; callout_reset_sbt( &sc->ctimeout, SBT_1S / rate, 0, scrn_timer, sc, C_PREL(1)); } } static int and_region(int *s1, int *e1, int s2, int e2) { if (*e1 < s2 || e2 < *s1) return FALSE; *s1 = imax(*s1, s2); *e1 = imin(*e1, e2); return TRUE; } static void scrn_update(scr_stat *scp, int show_cursor) { int start; int end; int s; int e; /* assert(scp == scp->sc->cur_scp) */ SC_VIDEO_LOCK(scp->sc); #ifndef SC_NO_CUTPASTE /* remove the previous mouse pointer image if necessary */ if (scp->status & MOUSE_VISIBLE) { s = scp->mouse_pos; e = scp->mouse_pos + scp->xsize + 1; if ((scp->status & (MOUSE_MOVED | MOUSE_HIDDEN)) || and_region(&s, &e, scp->start, scp->end) || ((scp->status & CURSOR_ENABLED) && (scp->cursor_pos != scp->cursor_oldpos) && (and_region(&s, &e, scp->cursor_pos, scp->cursor_pos) || and_region(&s, &e, scp->cursor_oldpos, scp->cursor_oldpos)))) { sc_remove_mouse_image(scp); if (scp->end >= scp->xsize * scp->ysize) scp->end = scp->xsize * scp->ysize - 1; } } #endif /* !SC_NO_CUTPASTE */ #if 1 /* debug: XXX */ if (scp->end >= scp->xsize * scp->ysize) { printf("scrn_update(): scp->end %d > size_of_screen!!\n", scp->end); scp->end = scp->xsize * scp->ysize - 1; } if (scp->start < 0) { printf("scrn_update(): scp->start %d < 0\n", scp->start); scp->start = 0; } #endif /* update screen image */ if (scp->start <= scp->end) { if (scp->mouse_cut_end >= 0) { /* there is a marked region for cut & paste */ if (scp->mouse_cut_start <= scp->mouse_cut_end) { start = scp->mouse_cut_start; end = scp->mouse_cut_end; } else { start = scp->mouse_cut_end; end = scp->mouse_cut_start - 1; } s = start; e = end; /* does the cut-mark region overlap with the update * region? */ if (and_region(&s, &e, scp->start, scp->end)) { (*scp->rndr->draw)(scp, s, e - s + 1, TRUE); s = 0; e = start - 1; if (and_region(&s, &e, scp->start, scp->end)) (*scp->rndr->draw)( scp, s, e - s + 1, FALSE); s = end + 1; e = scp->xsize * scp->ysize - 1; if (and_region(&s, &e, scp->start, scp->end)) (*scp->rndr->draw)( scp, s, e - s + 1, FALSE); } else { (*scp->rndr->draw)(scp, scp->start, scp->end - scp->start + 1, FALSE); } } else { (*scp->rndr->draw)( scp, scp->start, scp->end - scp->start + 1, FALSE); } } /* we are not to show the cursor and the mouse pointer... */ if (!show_cursor) { scp->end = 0; scp->start = scp->xsize * scp->ysize - 1; SC_VIDEO_UNLOCK(scp->sc); return; } /* update cursor image */ if (scp->status & CURSOR_ENABLED) { s = scp->start; e = scp->end; /* did cursor move since last time ? */ if (scp->cursor_pos != scp->cursor_oldpos) { /* do we need to remove old cursor image ? */ if (!and_region( &s, &e, scp->cursor_oldpos, scp->cursor_oldpos)) sc_remove_cursor_image(scp); sc_draw_cursor_image(scp); } else { if (and_region( &s, &e, scp->cursor_pos, scp->cursor_pos)) /* cursor didn't move, but has been overwritten */ sc_draw_cursor_image(scp); else if (scp->curs_attr.flags & CONS_BLINK_CURSOR) /* if it's a blinking cursor, update it */ (*scp->rndr->blink_cursor)(scp, scp->cursor_pos, sc_inside_cutmark(scp, scp->cursor_pos)); } } #ifndef SC_NO_CUTPASTE /* update "pseudo" mouse pointer image */ if (scp->sc->flags & SC_MOUSE_ENABLED) { if (!(scp->status & (MOUSE_VISIBLE | MOUSE_HIDDEN))) { scp->status &= ~MOUSE_MOVED; sc_draw_mouse_image(scp); } } #endif /* SC_NO_CUTPASTE */ scp->end = 0; scp->start = scp->xsize * scp->ysize - 1; SC_VIDEO_UNLOCK(scp->sc); } #ifdef DEV_SPLASH static int scsplash_callback(int event, void *arg) { sc_softc_t *sc; int error; sc = (sc_softc_t *)arg; switch (event) { case SPLASH_INIT: if (add_scrn_saver(scsplash_saver) == 0) { sc->flags &= ~SC_SAVER_FAILED; run_scrn_saver = TRUE; if (cold && !(boothowto & RB_VERBOSE)) { scsplash_stick(TRUE); (*current_saver)(sc, TRUE); } } return 0; case SPLASH_TERM: if (current_saver == scsplash_saver) { scsplash_stick(FALSE); error = remove_scrn_saver(scsplash_saver); if (error) return error; } return 0; default: return EINVAL; } } static void scsplash_saver(sc_softc_t *sc, int show) { static int busy = FALSE; scr_stat *scp; if (busy) return; busy = TRUE; scp = sc->cur_scp; if (show) { if (!(sc->flags & SC_SAVER_FAILED)) { if (!(sc->flags & SC_SCRN_BLANKED)) set_scrn_saver_mode(scp, -1, NULL, 0); switch (splash(sc->adp, TRUE)) { case 0: /* succeeded */ break; case EAGAIN: /* try later */ restore_scrn_saver_mode(scp, FALSE); sc_touch_scrn_saver(); /* XXX */ break; default: sc->flags |= SC_SAVER_FAILED; scsplash_stick(FALSE); restore_scrn_saver_mode(scp, TRUE); printf( "scsplash_saver(): failed to put up the image\n"); break; } } } else if (!sticky_splash) { if ((sc->flags & SC_SCRN_BLANKED) && (splash(sc->adp, FALSE) == 0)) restore_scrn_saver_mode(scp, TRUE); } busy = FALSE; } static int add_scrn_saver(void (*this_saver)(sc_softc_t *, int)) { #if 0 int error; if (current_saver != none_saver) { error = remove_scrn_saver(current_saver); if (error) return error; } #endif if (current_saver != none_saver) return EBUSY; run_scrn_saver = FALSE; saver_mode = CONS_LKM_SAVER; current_saver = this_saver; return 0; } static int remove_scrn_saver(void (*this_saver)(sc_softc_t *, int)) { if (current_saver != this_saver) return EINVAL; #if 0 /* * In order to prevent `current_saver' from being called by * the timeout routine `scrn_timer()' while we manipulate * the saver list, we shall set `current_saver' to `none_saver' * before stopping the current saver, rather than blocking by `splXX()'. */ current_saver = none_saver; if (scrn_blanked) stop_scrn_saver(this_saver); #endif /* unblank all blanked screens */ wait_scrn_saver_stop(NULL); if (scrn_blanked) return EBUSY; current_saver = none_saver; return 0; } static int set_scrn_saver_mode(scr_stat *scp, int mode, u_char *pal, int border) { int s; /* assert(scp == scp->sc->cur_scp) */ s = spltty(); if (!ISGRAPHSC(scp)) sc_remove_cursor_image(scp); scp->splash_save_mode = scp->mode; scp->splash_save_status = scp->status & (GRAPHICS_MODE | PIXEL_MODE); scp->status &= ~(GRAPHICS_MODE | PIXEL_MODE); scp->status |= (UNKNOWN_MODE | SAVER_RUNNING); scp->sc->flags |= SC_SCRN_BLANKED; ++scrn_blanked; splx(s); if (mode < 0) return 0; scp->mode = mode; if (set_mode(scp) == 0) { if (scp->sc->adp->va_info.vi_flags & V_INFO_GRAPHICS) scp->status |= GRAPHICS_MODE; #ifndef SC_NO_PALETTE_LOADING if (pal != NULL) vidd_load_palette(scp->sc->adp, pal); #endif sc_set_border(scp, border); return 0; } else { s = spltty(); scp->mode = scp->splash_save_mode; scp->status &= ~(UNKNOWN_MODE | SAVER_RUNNING); scp->status |= scp->splash_save_status; splx(s); return 1; } } static int restore_scrn_saver_mode(scr_stat *scp, int changemode) { int mode; int status; int s; /* assert(scp == scp->sc->cur_scp) */ s = spltty(); mode = scp->mode; status = scp->status; scp->mode = scp->splash_save_mode; scp->status &= ~(UNKNOWN_MODE | SAVER_RUNNING); scp->status |= scp->splash_save_status; scp->sc->flags &= ~SC_SCRN_BLANKED; if (!changemode) { if (!ISGRAPHSC(scp)) sc_draw_cursor_image(scp); --scrn_blanked; splx(s); return 0; } if (set_mode(scp) == 0) { #ifndef SC_NO_PALETTE_LOADING #ifdef SC_PIXEL_MODE if (scp->sc->adp->va_info.vi_mem_model == V_INFO_MM_DIRECT) vidd_load_palette(scp->sc->adp, scp->sc->palette2); else #endif vidd_load_palette(scp->sc->adp, scp->sc->palette); #endif --scrn_blanked; splx(s); return 0; } else { scp->mode = mode; scp->status = status; splx(s); return 1; } } static void stop_scrn_saver(sc_softc_t *sc, void (*saver)(sc_softc_t *, int)) { (*saver)(sc, FALSE); run_scrn_saver = FALSE; /* the screen saver may have chosen not to stop after all... */ if (sc->flags & SC_SCRN_BLANKED) return; mark_all(sc->cur_scp); if (sc->delayed_next_scr) sc_switch_scr(sc, sc->delayed_next_scr - 1); if (!kdb_active) wakeup(&scrn_blanked); } static int wait_scrn_saver_stop(sc_softc_t *sc) { int error = 0; while (scrn_blanked > 0) { run_scrn_saver = FALSE; if (sc && !(sc->flags & SC_SCRN_BLANKED)) { error = 0; break; } error = tsleep(&scrn_blanked, PZERO | PCATCH, "scrsav", 0); if ((error != 0) && (error != ERESTART)) break; } run_scrn_saver = FALSE; return error; } #endif /* DEV_SPLASH */ void sc_touch_scrn_saver(void) { scsplash_stick(FALSE); run_scrn_saver = FALSE; } int sc_switch_scr(sc_softc_t *sc, u_int next_scr) { scr_stat *cur_scp; struct tty *tp; struct proc *p; int s; DPRINTF(5, ("sc0: sc_switch_scr() %d ", next_scr + 1)); if (sc->cur_scp == NULL) return (0); /* prevent switch if previously requested */ if (sc->flags & SC_SCRN_VTYLOCK) { sc_bell(sc->cur_scp, sc->cur_scp->bell_pitch, sc->cur_scp->bell_duration); return EPERM; } /* delay switch if the screen is blanked or being updated */ if ((sc->flags & SC_SCRN_BLANKED) || sc->write_in_progress || sc->blink_in_progress) { sc->delayed_next_scr = next_scr + 1; sc_touch_scrn_saver(); DPRINTF(5, ("switch delayed\n")); return 0; } sc->delayed_next_scr = 0; s = spltty(); cur_scp = sc->cur_scp; /* we are in the middle of the vty switching process... */ if (sc->switch_in_progress && (cur_scp->smode.mode == VT_PROCESS) && cur_scp->proc) { p = pfind(cur_scp->pid); if (cur_scp->proc != p) { if (p) PROC_UNLOCK(p); /* * The controlling process has died!!. Do some clean * up. NOTE:`cur_scp->proc' and `cur_scp->smode.mode' * are not reset here yet; they will be cleared later. */ DPRINTF(5, ("cur_scp controlling process %d died, ", cur_scp->pid)); if (cur_scp->status & SWITCH_WAIT_REL) { /* * Force the previous switch to finish, but * return now with error. */ DPRINTF(5, ("reset WAIT_REL, ")); finish_vt_rel(cur_scp, TRUE, &s); splx(s); DPRINTF(5, ("finishing previous switch\n")); return EINVAL; } else if (cur_scp->status & SWITCH_WAIT_ACQ) { /* let's assume screen switch has been * completed. */ DPRINTF(5, ("reset WAIT_ACQ, ")); finish_vt_acq(cur_scp); } else { /* * We are in between screen release and * acquisition, and reached here via scgetc() or * scrn_timer() which has interrupted * exchange_scr(). Don't do anything stupid. */ DPRINTF(5, ("waiting nothing, ")); } } else { if (p) PROC_UNLOCK(p); /* * The controlling process is alive, but not * responding... It is either buggy or it may be just * taking time. The following code is a gross kludge to * cope with this problem for which there is no clean * solution. XXX */ if (cur_scp->status & SWITCH_WAIT_REL) { switch (sc->switch_in_progress++) { case 1: break; case 2: DPRINTF(5, ("sending relsig again, ")); signal_vt_rel(cur_scp); break; case 3: break; case 4: default: /* * Act as if the controlling program * returned VT_FALSE. */ DPRINTF(5, ("force reset WAIT_REL, ")); finish_vt_rel(cur_scp, FALSE, &s); splx(s); DPRINTF(5, ("act as if VT_FALSE was seen\n")); return EINVAL; } } else if (cur_scp->status & SWITCH_WAIT_ACQ) { switch (sc->switch_in_progress++) { case 1: break; case 2: DPRINTF(5, ("sending acqsig again, ")); signal_vt_acq(cur_scp); break; case 3: break; case 4: default: /* clear the flag and finish the * previous switch */ DPRINTF(5, ("force reset WAIT_ACQ, ")); finish_vt_acq(cur_scp); break; } } } } /* * Return error if an invalid argument is given, or vty switch * is still in progress. */ if ((next_scr < sc->first_vty) || (next_scr >= sc->first_vty + sc->vtys) || sc->switch_in_progress) { splx(s); sc_bell(cur_scp, bios_value.bell_pitch, BELL_DURATION); DPRINTF(5, ("error 1\n")); return EINVAL; } /* * Don't allow switching away from the graphics mode vty * if the switch mode is VT_AUTO, unless the next vty is the same * as the current or the current vty has been closed (but showing). */ tp = SC_DEV(sc, cur_scp->index); if ((cur_scp->index != next_scr) && tty_opened_ns(tp) && (cur_scp->smode.mode == VT_AUTO) && ISGRAPHSC(cur_scp)) { splx(s); sc_bell(cur_scp, bios_value.bell_pitch, BELL_DURATION); DPRINTF(5, ("error, graphics mode\n")); return EINVAL; } /* * Is the wanted vty open? Don't allow switching to a closed vty. * If we are in DDB, don't switch to a vty in the VT_PROCESS mode. * Note that we always allow the user to switch to the kernel * console even if it is closed. */ if ((sc_console == NULL) || (next_scr != sc_console->index)) { tp = SC_DEV(sc, next_scr); if (!tty_opened_ns(tp)) { splx(s); sc_bell(cur_scp, bios_value.bell_pitch, BELL_DURATION); DPRINTF(5, ("error 2, requested vty isn't open!\n")); return EINVAL; } if (kdb_active && SC_STAT(tp)->smode.mode == VT_PROCESS) { splx(s); DPRINTF(5, ("error 3, requested vty is in the VT_PROCESS mode\n")); return EINVAL; } } /* this is the start of vty switching process... */ ++sc->switch_in_progress; sc->old_scp = cur_scp; sc->new_scp = sc_get_stat(SC_DEV(sc, next_scr)); if (sc->new_scp == sc->old_scp) { sc->switch_in_progress = 0; /* * XXX wakeup() locks the scheduler lock which will hang if * the lock is in an in-between state, e.g., when we stop at * a breakpoint at fork_exit. It has always been wrong to call * wakeup() when the debugger is active. In RELENG_4, wakeup() * is supposed to be locked by splhigh(), but the debugger may * be invoked at splhigh(). */ if (!kdb_active) wakeup(VTY_WCHAN(sc, next_scr)); splx(s); DPRINTF(5, ("switch done (new == old)\n")); return 0; } /* has controlling process died? */ vt_proc_alive(sc->old_scp); vt_proc_alive(sc->new_scp); /* wait for the controlling process to release the screen, if necessary */ if (signal_vt_rel(sc->old_scp)) { splx(s); return 0; } /* go set up the new vty screen */ splx(s); exchange_scr(sc); s = spltty(); /* wake up processes waiting for this vty */ if (!kdb_active) wakeup(VTY_WCHAN(sc, next_scr)); /* wait for the controlling process to acknowledge, if necessary */ if (signal_vt_acq(sc->cur_scp)) { splx(s); return 0; } sc->switch_in_progress = 0; if (sc->unit == sc_console_unit) cnavailable(sc_consptr, TRUE); splx(s); DPRINTF(5, ("switch done\n")); return 0; } static int do_switch_scr(sc_softc_t *sc, int s) { vt_proc_alive(sc->new_scp); splx(s); exchange_scr(sc); s = spltty(); /* sc->cur_scp == sc->new_scp */ wakeup(VTY_WCHAN(sc, sc->cur_scp->index)); /* wait for the controlling process to acknowledge, if necessary */ if (!signal_vt_acq(sc->cur_scp)) { sc->switch_in_progress = 0; if (sc->unit == sc_console_unit) cnavailable(sc_consptr, TRUE); } return s; } static int vt_proc_alive(scr_stat *scp) { struct proc *p; if (scp->proc) { if ((p = pfind(scp->pid)) != NULL) PROC_UNLOCK(p); if (scp->proc == p) return TRUE; scp->proc = NULL; scp->smode.mode = VT_AUTO; DPRINTF(5, ("vt controlling process %d died\n", scp->pid)); } return FALSE; } static int signal_vt_rel(scr_stat *scp) { if (scp->smode.mode != VT_PROCESS) return FALSE; scp->status |= SWITCH_WAIT_REL; PROC_LOCK(scp->proc); kern_psignal(scp->proc, scp->smode.relsig); PROC_UNLOCK(scp->proc); DPRINTF(5, ("sending relsig to %d\n", scp->pid)); return TRUE; } static int signal_vt_acq(scr_stat *scp) { if (scp->smode.mode != VT_PROCESS) return FALSE; if (scp->sc->unit == sc_console_unit) cnavailable(sc_consptr, FALSE); scp->status |= SWITCH_WAIT_ACQ; PROC_LOCK(scp->proc); kern_psignal(scp->proc, scp->smode.acqsig); PROC_UNLOCK(scp->proc); DPRINTF(5, ("sending acqsig to %d\n", scp->pid)); return TRUE; } static int finish_vt_rel(scr_stat *scp, int release, int *s) { if (scp == scp->sc->old_scp && scp->status & SWITCH_WAIT_REL) { scp->status &= ~SWITCH_WAIT_REL; if (release) *s = do_switch_scr(scp->sc, *s); else scp->sc->switch_in_progress = 0; return 0; } return EINVAL; } static int finish_vt_acq(scr_stat *scp) { if (scp == scp->sc->new_scp && scp->status & SWITCH_WAIT_ACQ) { scp->status &= ~SWITCH_WAIT_ACQ; scp->sc->switch_in_progress = 0; return 0; } return EINVAL; } static void exchange_scr(sc_softc_t *sc) { scr_stat *scp; /* save the current state of video and keyboard */ sc_move_cursor(sc->old_scp, sc->old_scp->xpos, sc->old_scp->ypos); if (!ISGRAPHSC(sc->old_scp)) sc_remove_cursor_image(sc->old_scp); if (sc->old_scp->kbd_mode == K_XLATE) save_kbd_state(sc->old_scp); /* set up the video for the new screen */ scp = sc->cur_scp = sc->new_scp; if (sc->old_scp->mode != scp->mode || ISUNKNOWNSC(sc->old_scp)) set_mode(scp); else sc_vtb_init(&scp->scr, VTB_FRAMEBUFFER, scp->xsize, scp->ysize, (void *)sc->adp->va_window, FALSE); scp->status |= MOUSE_HIDDEN; sc_move_cursor(scp, scp->xpos, scp->ypos); if (!ISGRAPHSC(scp)) sc_set_cursor_image(scp); #ifndef SC_NO_PALETTE_LOADING if (ISGRAPHSC(sc->old_scp)) { #ifdef SC_PIXEL_MODE if (sc->adp->va_info.vi_mem_model == V_INFO_MM_DIRECT) vidd_load_palette(sc->adp, sc->palette2); else #endif vidd_load_palette(sc->adp, sc->palette); } #endif sc_set_border(scp, scp->border); /* set up the keyboard for the new screen */ if (sc->kbd_open_level == 0 && sc->old_scp->kbd_mode != scp->kbd_mode) (void)kbdd_ioctl(sc->kbd, KDSKBMODE, (caddr_t)&scp->kbd_mode); update_kbd_state(scp, scp->status, LOCK_MASK); mark_all(scp); } static void sc_puts(scr_stat *scp, u_char *buf, int len) { #ifdef DEV_SPLASH /* make screensaver happy */ if (!sticky_splash && scp == scp->sc->cur_scp && !sc_saver_keyb_only) run_scrn_saver = FALSE; #endif if (scp->tsw) (*scp->tsw->te_puts)(scp, buf, len); if (scp->sc->delayed_next_scr) sc_switch_scr(scp->sc, scp->sc->delayed_next_scr - 1); } void sc_draw_cursor_image(scr_stat *scp) { /* assert(scp == scp->sc->cur_scp); */ SC_VIDEO_LOCK(scp->sc); (*scp->rndr->draw_cursor)(scp, scp->cursor_pos, scp->curs_attr.flags & CONS_BLINK_CURSOR, TRUE, sc_inside_cutmark(scp, scp->cursor_pos)); scp->cursor_oldpos = scp->cursor_pos; SC_VIDEO_UNLOCK(scp->sc); } void sc_remove_cursor_image(scr_stat *scp) { /* assert(scp == scp->sc->cur_scp); */ SC_VIDEO_LOCK(scp->sc); (*scp->rndr->draw_cursor)(scp, scp->cursor_oldpos, scp->curs_attr.flags & CONS_BLINK_CURSOR, FALSE, sc_inside_cutmark(scp, scp->cursor_oldpos)); SC_VIDEO_UNLOCK(scp->sc); } static void update_cursor_image(scr_stat *scp) { /* assert(scp == scp->sc->cur_scp); */ sc_remove_cursor_image(scp); sc_set_cursor_image(scp); sc_draw_cursor_image(scp); } void sc_set_cursor_image(scr_stat *scp) { scp->curs_attr = scp->base_curs_attr; if (scp->curs_attr.flags & CONS_HIDDEN_CURSOR) { /* hidden cursor is internally represented as zero-height * underline */ scp->curs_attr.flags = CONS_CHAR_CURSOR; scp->curs_attr.base = scp->curs_attr.height = 0; } else if (scp->curs_attr.flags & CONS_CHAR_CURSOR) { scp->curs_attr.base = imin(scp->base_curs_attr.base, scp->font_size - 1); scp->curs_attr.height = imin(scp->base_curs_attr.height, scp->font_size - scp->curs_attr.base); } else { /* block cursor */ scp->curs_attr.base = 0; scp->curs_attr.height = scp->font_size; } /* assert(scp == scp->sc->cur_scp); */ SC_VIDEO_LOCK(scp->sc); (*scp->rndr->set_cursor)(scp, scp->curs_attr.base, scp->curs_attr.height, scp->curs_attr.flags & CONS_BLINK_CURSOR); SC_VIDEO_UNLOCK(scp->sc); } static void sc_adjust_ca(struct cursor_attr *cap, int flags, int base, int height) { if (flags & CONS_CHARCURSOR_COLORS) { cap->bg[0] = base & 0xff; cap->bg[1] = height & 0xff; } else if (flags & CONS_MOUSECURSOR_COLORS) { cap->mouse_ba = base & 0xff; cap->mouse_ia = height & 0xff; } else { if (base >= 0) cap->base = base; if (height >= 0) cap->height = height; if (!(flags & CONS_SHAPEONLY_CURSOR)) cap->flags = flags & CONS_CURSOR_ATTRS; } } static void change_cursor_shape(scr_stat *scp, int flags, int base, int height) { if ((scp == scp->sc->cur_scp) && !ISGRAPHSC(scp)) sc_remove_cursor_image(scp); if (flags & CONS_RESET_CURSOR) scp->base_curs_attr = scp->dflt_curs_attr; else if (flags & CONS_DEFAULT_CURSOR) { sc_adjust_ca(&scp->dflt_curs_attr, flags, base, height); scp->base_curs_attr = scp->dflt_curs_attr; } else sc_adjust_ca(&scp->base_curs_attr, flags, base, height); if ((scp == scp->sc->cur_scp) && !ISGRAPHSC(scp)) { sc_set_cursor_image(scp); sc_draw_cursor_image(scp); } } void sc_change_cursor_shape(scr_stat *scp, int flags, int base, int height) { sc_softc_t *sc; struct tty *tp; int s; int i; if (flags == -1) flags = CONS_SHAPEONLY_CURSOR; s = spltty(); if (flags & CONS_LOCAL_CURSOR) { /* local (per vty) change */ change_cursor_shape(scp, flags, base, height); splx(s); return; } /* global change */ sc = scp->sc; if (flags & CONS_RESET_CURSOR) sc->curs_attr = sc->dflt_curs_attr; else if (flags & CONS_DEFAULT_CURSOR) { sc_adjust_ca(&sc->dflt_curs_attr, flags, base, height); sc->curs_attr = sc->dflt_curs_attr; } else sc_adjust_ca(&sc->curs_attr, flags, base, height); for (i = sc->first_vty; i < sc->first_vty + sc->vtys; ++i) { if ((tp = SC_DEV(sc, i)) == NULL) continue; if ((scp = sc_get_stat(tp)) == NULL) continue; scp->dflt_curs_attr = sc->curs_attr; change_cursor_shape(scp, CONS_RESET_CURSOR, -1, -1); } splx(s); } static void scinit(int unit, int flags) { /* * When syscons is being initialized as the kernel console, malloc() * is not yet functional, because various kernel structures has not been * fully initialized yet. Therefore, we need to declare the following * static buffers for the console. This is less than ideal, * but is necessry evil for the time being. XXX */ static u_short sc_buffer[ROW * COL]; /* XXX */ #ifndef SC_NO_FONT_LOADING static u_char font_8[256 * 8]; static u_char font_14[256 * 14]; static u_char font_16[256 * 16]; #endif #ifdef SC_KERNEL_CONS_ATTRS static const u_char dflt_kattrtab[] = SC_KERNEL_CONS_ATTRS; #elif SC_KERNEL_CONS_ATTR == FG_WHITE static const u_char dflt_kattrtab[] = { FG_WHITE, FG_YELLOW, FG_LIGHTMAGENTA, FG_LIGHTRED, FG_LIGHTCYAN, FG_LIGHTGREEN, FG_LIGHTBLUE, FG_GREEN, 0, }; #else static const u_char dflt_kattrtab[] = { FG_WHITE, 0, }; #endif sc_softc_t *sc; scr_stat *scp; video_adapter_t *adp; int col; int kbdidx; int row; int i; /* one time initialization */ if (init_done == COLD) { sc_get_bios_values(&bios_value); for (i = 0; i < nitems(sc_kattrtab); i++) sc_kattrtab[i] = dflt_kattrtab[i % (nitems(dflt_kattrtab) - 1)]; } init_done = WARM; /* * Allocate resources. Even if we are being called for the second * time, we must allocate them again, because they might have * disappeared... */ sc = sc_get_softc(unit, flags & SC_KERNEL_CONSOLE); if ((sc->flags & SC_INIT_DONE) == 0) SC_VIDEO_LOCKINIT(sc); adp = NULL; if (sc->adapter >= 0) { vid_release(sc->adp, (void *)&sc->adapter); adp = sc->adp; sc->adp = NULL; } if (sc->kbd != NULL) { DPRINTF(5, ("sc%d: releasing kbd%d\n", unit, sc->kbd->kb_index)); i = kbd_release(sc->kbd, (void *)&sc->kbd); DPRINTF(5, ("sc%d: kbd_release returned %d\n", unit, i)); if (sc->kbd != NULL) { DPRINTF(5, ("sc%d: kbd != NULL!, index:%d, unit:%d, flags:0x%x\n", unit, sc->kbd->kb_index, sc->kbd->kb_unit, sc->kbd->kb_flags)); } sc->kbd = NULL; } sc->adapter = vid_allocate("*", unit, (void *)&sc->adapter); sc->adp = vid_get_adapter(sc->adapter); /* assert((sc->adapter >= 0) && (sc->adp != NULL)) */ kbdidx = sc_allocate_keyboard(sc, unit); DPRINTF(1, ("sc%d: keyboard %d\n", unit, kbdidx)); sc->kbd = kbd_get_keyboard(kbdidx); if (sc->kbd != NULL) { DPRINTF(1, ("sc%d: kbd index:%d, unit:%d, flags:0x%x\n", unit, sc->kbd->kb_index, sc->kbd->kb_unit, sc->kbd->kb_flags)); } if (!(sc->flags & SC_INIT_DONE) || (adp != sc->adp)) { sc->initial_mode = sc->adp->va_initial_mode; #ifndef SC_NO_FONT_LOADING if (flags & SC_KERNEL_CONSOLE) { sc->font_8 = font_8; sc->font_14 = font_14; sc->font_16 = font_16; } else if (sc->font_8 == NULL) { /* assert(sc_malloc) */ sc->font_8 = malloc(sizeof(font_8), M_DEVBUF, M_WAITOK); sc->font_14 = malloc(sizeof(font_14), M_DEVBUF, M_WAITOK); sc->font_16 = malloc(sizeof(font_16), M_DEVBUF, M_WAITOK); } #endif /* extract the hardware cursor location and hide the cursor for * now */ vidd_read_hw_cursor(sc->adp, &col, &row); vidd_set_hw_cursor(sc->adp, -1, -1); /* set up the first console */ sc->first_vty = unit * MAXCONS; sc->vtys = MAXCONS; /* XXX: should be configurable */ if (flags & SC_KERNEL_CONSOLE) { /* * Set up devs structure but don't use it yet, calling * make_dev() might panic kernel. Wait for * sc_attach_unit() to actually create the devices. */ sc->dev = main_devs; scp = &main_console; init_scp(sc, sc->first_vty, scp); sc_vtb_init(&scp->vtb, VTB_MEMORY, scp->xsize, scp->ysize, (void *)sc_buffer, FALSE); if (sc_init_emulator(scp, SC_DFLT_TERM)) sc_init_emulator(scp, "*"); (*scp->tsw->te_default_attr)( scp, SC_KERNEL_CONS_ATTR, SC_KERNEL_CONS_REV_ATTR); } else { /* assert(sc_malloc) */ sc->dev = malloc(sizeof(struct tty *) * sc->vtys, M_DEVBUF, M_WAITOK | M_ZERO); sc->dev[0] = sc_alloc_tty(0, unit * MAXCONS); scp = alloc_scp(sc, sc->first_vty); SC_STAT(sc->dev[0]) = scp; } sc->cur_scp = scp; /* copy screen to temporary buffer */ sc_vtb_init(&scp->scr, VTB_FRAMEBUFFER, scp->xsize, scp->ysize, (void *)scp->sc->adp->va_window, FALSE); if (ISTEXTSC(scp)) sc_vtb_copy(&scp->scr, 0, &scp->vtb, 0, scp->xsize * scp->ysize); /* Sync h/w cursor position to s/w (sc and teken). */ if (col >= scp->xsize) col = 0; if (row >= scp->ysize) row = scp->ysize - 1; scp->xpos = col; scp->ypos = row; scp->cursor_pos = scp->cursor_oldpos = row * scp->xsize + col; (*scp->tsw->te_sync)(scp); sc->dflt_curs_attr.base = 0; sc->dflt_curs_attr.height = howmany(scp->font_size, 8); sc->dflt_curs_attr.flags = 0; sc->dflt_curs_attr.bg[0] = FG_RED; sc->dflt_curs_attr.bg[1] = FG_LIGHTGREY; sc->dflt_curs_attr.bg[2] = FG_BLUE; sc->dflt_curs_attr.mouse_ba = FG_WHITE; sc->dflt_curs_attr.mouse_ia = FG_RED; sc->curs_attr = sc->dflt_curs_attr; scp->base_curs_attr = scp->dflt_curs_attr = sc->curs_attr; scp->curs_attr = scp->base_curs_attr; #ifndef SC_NO_SYSMOUSE sc_mouse_move(scp, scp->xpixel / 2, scp->ypixel / 2); #endif if (!ISGRAPHSC(scp)) { sc_set_cursor_image(scp); sc_draw_cursor_image(scp); } /* save font and palette */ #ifndef SC_NO_FONT_LOADING sc->fonts_loaded = 0; if (ISFONTAVAIL(sc->adp->va_flags)) { #ifdef SC_DFLT_FONT bcopy(dflt_font_8, sc->font_8, sizeof(dflt_font_8)); bcopy(dflt_font_14, sc->font_14, sizeof(dflt_font_14)); bcopy(dflt_font_16, sc->font_16, sizeof(dflt_font_16)); sc->fonts_loaded = FONT_16 | FONT_14 | FONT_8; if (scp->font_size < 14) { sc_load_font(scp, 0, 8, 8, sc->font_8, 0, 256); } else if (scp->font_size >= 16) { sc_load_font( scp, 0, 16, 8, sc->font_16, 0, 256); } else { sc_load_font( scp, 0, 14, 8, sc->font_14, 0, 256); } #else /* !SC_DFLT_FONT */ if (scp->font_size < 14) { sc_save_font(scp, 0, 8, 8, sc->font_8, 0, 256); sc->fonts_loaded = FONT_8; } else if (scp->font_size >= 16) { sc_save_font( scp, 0, 16, 8, sc->font_16, 0, 256); sc->fonts_loaded = FONT_16; } else { sc_save_font( scp, 0, 14, 8, sc->font_14, 0, 256); sc->fonts_loaded = FONT_14; } #endif /* SC_DFLT_FONT */ /* FONT KLUDGE: always use the font page #0. XXX */ sc_show_font(scp, 0); } #endif /* !SC_NO_FONT_LOADING */ #ifndef SC_NO_PALETTE_LOADING vidd_save_palette(sc->adp, sc->palette); #ifdef SC_PIXEL_MODE for (i = 0; i < sizeof(sc->palette2); i++) sc->palette2[i] = i / 3; #endif #endif #ifdef DEV_SPLASH if (!(sc->flags & SC_SPLASH_SCRN)) { /* we are ready to put up the splash image! */ splash_init(sc->adp, scsplash_callback, sc); sc->flags |= SC_SPLASH_SCRN; } #endif } /* the rest is not necessary, if we have done it once */ if (sc->flags & SC_INIT_DONE) return; /* initialize mapscrn arrays to a one to one map */ for (i = 0; i < sizeof(sc->scr_map); i++) sc->scr_map[i] = sc->scr_rmap[i] = i; sc->flags |= SC_INIT_DONE; } static void scterm(int unit, int flags) { sc_softc_t *sc; scr_stat *scp; sc = sc_get_softc(unit, flags & SC_KERNEL_CONSOLE); if (sc == NULL) return; /* shouldn't happen */ #ifdef DEV_SPLASH /* this console is no longer available for the splash screen */ if (sc->flags & SC_SPLASH_SCRN) { splash_term(sc->adp); sc->flags &= ~SC_SPLASH_SCRN; } #endif #if 0 /* XXX */ /* move the hardware cursor to the upper-left corner */ vidd_set_hw_cursor(sc->adp, 0, 0); #endif /* release the keyboard and the video card */ if (sc->kbd != NULL) kbd_release(sc->kbd, &sc->kbd); if (sc->adapter >= 0) vid_release(sc->adp, &sc->adapter); /* stop the terminal emulator, if any */ scp = sc_get_stat(sc->dev[0]); if (scp->tsw) (*scp->tsw->te_term)(scp, &scp->ts); mtx_destroy(&sc->video_mtx); /* clear the structure */ if (!(flags & SC_KERNEL_CONSOLE)) { free(scp->ts, M_DEVBUF); /* XXX: We need delete_dev() for this */ free(sc->dev, M_DEVBUF); #if 0 /* XXX: We need a ttyunregister for this */ free(sc->tty, M_DEVBUF); #endif #ifndef SC_NO_FONT_LOADING free(sc->font_8, M_DEVBUF); free(sc->font_14, M_DEVBUF); free(sc->font_16, M_DEVBUF); #endif /* XXX vtb, history */ } bzero(sc, sizeof(*sc)); sc->adapter = -1; } static void scshutdown(__unused void *arg, __unused int howto) { KASSERT(sc_console != NULL, ("sc_console != NULL")); KASSERT(sc_console->sc != NULL, ("sc_console->sc != NULL")); KASSERT(sc_console->sc->cur_scp != NULL, ("sc_console->sc->cur_scp != NULL")); sc_touch_scrn_saver(); if (!cold && sc_console->sc->cur_scp->index != sc_console->index && sc_console->sc->cur_scp->smode.mode == VT_AUTO && sc_console->smode.mode == VT_AUTO) sc_switch_scr(sc_console->sc, sc_console->index); shutdown_in_progress = TRUE; } static void scsuspend(__unused void *arg) { int retry; KASSERT(sc_console != NULL, ("sc_console != NULL")); KASSERT(sc_console->sc != NULL, ("sc_console->sc != NULL")); KASSERT(sc_console->sc->cur_scp != NULL, ("sc_console->sc->cur_scp != NULL")); sc_susp_scr = sc_console->sc->cur_scp->index; if (sc_no_suspend_vtswitch || sc_susp_scr == sc_console->index) { sc_touch_scrn_saver(); sc_susp_scr = -1; return; } for (retry = 0; retry < 10; retry++) { sc_switch_scr(sc_console->sc, sc_console->index); if (!sc_console->sc->switch_in_progress) break; pause("scsuspend", hz); } suspend_in_progress = TRUE; } static void scresume(__unused void *arg) { KASSERT(sc_console != NULL, ("sc_console != NULL")); KASSERT(sc_console->sc != NULL, ("sc_console->sc != NULL")); KASSERT(sc_console->sc->cur_scp != NULL, ("sc_console->sc->cur_scp != NULL")); suspend_in_progress = FALSE; if (sc_susp_scr < 0) { update_font(sc_console->sc->cur_scp); return; } sc_switch_scr(sc_console->sc, sc_susp_scr); } int sc_clean_up(scr_stat *scp) { #ifdef DEV_SPLASH int error; #endif if (scp->sc->flags & SC_SCRN_BLANKED) { sc_touch_scrn_saver(); #ifdef DEV_SPLASH if ((error = wait_scrn_saver_stop(scp->sc))) return error; #endif } scp->status |= MOUSE_HIDDEN; sc_remove_mouse_image(scp); sc_remove_cutmarking(scp); return 0; } void sc_alloc_scr_buffer(scr_stat *scp, int wait, int discard) { sc_vtb_t new; sc_vtb_t old; old = scp->vtb; sc_vtb_init(&new, VTB_MEMORY, scp->xsize, scp->ysize, NULL, wait); if (!discard && (old.vtb_flags & VTB_VALID)) { /* retain the current cursor position and buffer contants */ scp->cursor_oldpos = scp->cursor_pos; /* * This works only if the old buffer has the same size as or * larger than the new one. XXX */ sc_vtb_copy(&old, 0, &new, 0, scp->xsize * scp->ysize); scp->vtb = new; } else { scp->vtb = new; sc_vtb_destroy(&old); } #ifndef SC_NO_SYSMOUSE /* move the mouse cursor at the center of the screen */ sc_mouse_move(scp, scp->xpixel / 2, scp->ypixel / 2); #endif } static scr_stat * alloc_scp(sc_softc_t *sc, int vty) { scr_stat *scp; /* assert(sc_malloc) */ scp = (scr_stat *)malloc(sizeof(scr_stat), M_DEVBUF, M_WAITOK); init_scp(sc, vty, scp); sc_alloc_scr_buffer(scp, TRUE, TRUE); if (sc_init_emulator(scp, SC_DFLT_TERM)) sc_init_emulator(scp, "*"); #ifndef SC_NO_CUTPASTE sc_alloc_cut_buffer(scp, TRUE); #endif #ifndef SC_NO_HISTORY sc_alloc_history_buffer(scp, 0, 0, TRUE); #endif return scp; } static void init_scp(sc_softc_t *sc, int vty, scr_stat *scp) { video_info_t info; bzero(scp, sizeof(*scp)); scp->index = vty; scp->sc = sc; scp->status = 0; scp->mode = sc->initial_mode; vidd_get_info(sc->adp, scp->mode, &info); if (info.vi_flags & V_INFO_GRAPHICS) { scp->status |= GRAPHICS_MODE; scp->xpixel = info.vi_width; scp->ypixel = info.vi_height; scp->xsize = info.vi_width / info.vi_cwidth; scp->ysize = info.vi_height / info.vi_cheight; scp->font_size = 0; scp->font = NULL; } else { scp->xsize = info.vi_width; scp->ysize = info.vi_height; scp->xpixel = scp->xsize * info.vi_cwidth; scp->ypixel = scp->ysize * info.vi_cheight; } scp->font_size = info.vi_cheight; scp->font_width = info.vi_cwidth; #ifndef SC_NO_FONT_LOADING if (info.vi_cheight < 14) scp->font = sc->font_8; else if (info.vi_cheight >= 16) scp->font = sc->font_16; else scp->font = sc->font_14; #else scp->font = NULL; #endif sc_vtb_init(&scp->vtb, VTB_MEMORY, 0, 0, NULL, FALSE); sc_vtb_init(&scp->scr, VTB_FRAMEBUFFER, 0, 0, NULL, FALSE); scp->xoff = scp->yoff = 0; scp->xpos = scp->ypos = 0; scp->start = scp->xsize * scp->ysize - 1; scp->end = 0; scp->tsw = NULL; scp->ts = NULL; scp->rndr = NULL; scp->border = (SC_NORM_ATTR >> 4) & 0x0f; scp->base_curs_attr = scp->dflt_curs_attr = sc->curs_attr; scp->mouse_cut_start = scp->xsize * scp->ysize; scp->mouse_cut_end = -1; scp->mouse_signal = 0; scp->mouse_pid = 0; scp->mouse_proc = NULL; scp->kbd_mode = K_XLATE; scp->bell_pitch = bios_value.bell_pitch; scp->bell_duration = BELL_DURATION; scp->status |= (bios_value.shift_state & NLKED); scp->status |= CURSOR_ENABLED | MOUSE_HIDDEN; scp->pid = 0; scp->proc = NULL; scp->smode.mode = VT_AUTO; scp->history = NULL; scp->history_pos = 0; scp->history_size = 0; } int sc_init_emulator(scr_stat *scp, char *name) { sc_term_sw_t *sw; sc_rndr_sw_t *rndr; void *p; int error; if (name == NULL) /* if no name is given, use the current emulator */ sw = scp->tsw; else /* ...otherwise find the named emulator */ sw = sc_term_match(name); if (sw == NULL) return EINVAL; rndr = NULL; if (strcmp(sw->te_renderer, "*") != 0) { rndr = sc_render_match(scp, sw->te_renderer, scp->status & (GRAPHICS_MODE | PIXEL_MODE)); } if (rndr == NULL) { rndr = sc_render_match(scp, scp->sc->adp->va_name, scp->status & (GRAPHICS_MODE | PIXEL_MODE)); if (rndr == NULL) return ENODEV; } if (sw == scp->tsw) { error = (*sw->te_init)(scp, &scp->ts, SC_TE_WARM_INIT); scp->rndr = rndr; scp->rndr->init(scp); sc_clear_screen(scp); /* assert(error == 0); */ return error; } if (sc_malloc && (sw->te_size > 0)) p = malloc(sw->te_size, M_DEVBUF, M_NOWAIT); else p = NULL; error = (*sw->te_init)(scp, &p, SC_TE_COLD_INIT); if (error) return error; if (scp->tsw) (*scp->tsw->te_term)(scp, &scp->ts); if (scp->ts != NULL) free(scp->ts, M_DEVBUF); scp->tsw = sw; scp->ts = p; scp->rndr = rndr; scp->rndr->init(scp); (*sw->te_default_attr)(scp, SC_NORM_ATTR, SC_NORM_REV_ATTR); sc_clear_screen(scp); return 0; } /* * scgetc(flags) - get character from keyboard. * If flags & SCGETC_CN, then avoid harmful side effects. * If flags & SCGETC_NONBLOCK, then wait until a key is pressed, else * return NOKEY if there is nothing there. */ static u_int scgetc(sc_softc_t *sc, u_int flags, struct sc_cnstate *sp) { scr_stat *scp; #ifndef SC_NO_HISTORY struct tty *tp; #endif u_int c; int this_scr; int f; int i; if (sc->kbd == NULL) return NOKEY; next_code: #if 1 /* I don't like this, but... XXX */ if (flags & SCGETC_CN) sccnupdate(sc->cur_scp); #endif scp = sc->cur_scp; /* first see if there is something in the keyboard port */ for (;;) { if (flags & SCGETC_CN) sccnscrunlock(sc, sp); c = kbdd_read_char(sc->kbd, !(flags & SCGETC_NONBLOCK)); if (flags & SCGETC_CN) sccnscrlock(sc, sp); if (c == ERRKEY) { if (!(flags & SCGETC_CN)) sc_bell( scp, bios_value.bell_pitch, BELL_DURATION); } else if (c == NOKEY) return c; else break; } /* make screensaver happy */ if (!(c & RELKEY)) sc_touch_scrn_saver(); if (!(flags & SCGETC_CN)) random_harvest_queue(&c, sizeof(c), RANDOM_KEYBOARD); if (sc->kbd_open_level == 0 && scp->kbd_mode != K_XLATE) return KEYCHAR(c); /* if scroll-lock pressed allow history browsing */ if (!ISGRAPHSC(scp) && scp->history && scp->status & SLKED) { scp->status &= ~CURSOR_ENABLED; sc_remove_cursor_image(scp); #ifndef SC_NO_HISTORY if (!(scp->status & BUFFER_SAVED)) { scp->status |= BUFFER_SAVED; sc_hist_save(scp); } switch (c) { /* FIXME: key codes */ case SPCLKEY | FKEY | F(49): /* home key */ sc_remove_cutmarking(scp); sc_hist_home(scp); goto next_code; case SPCLKEY | FKEY | F(57): /* end key */ sc_remove_cutmarking(scp); sc_hist_end(scp); goto next_code; case SPCLKEY | FKEY | F(50): /* up arrow key */ sc_remove_cutmarking(scp); if (sc_hist_up_line(scp)) if (!(flags & SCGETC_CN)) sc_bell(scp, bios_value.bell_pitch, BELL_DURATION); goto next_code; case SPCLKEY | FKEY | F(58): /* down arrow key */ sc_remove_cutmarking(scp); if (sc_hist_down_line(scp)) if (!(flags & SCGETC_CN)) sc_bell(scp, bios_value.bell_pitch, BELL_DURATION); goto next_code; case SPCLKEY | FKEY | F(51): /* page up key */ sc_remove_cutmarking(scp); for (i = 0; i < scp->ysize; i++) if (sc_hist_up_line(scp)) { if (!(flags & SCGETC_CN)) sc_bell(scp, bios_value.bell_pitch, BELL_DURATION); break; } goto next_code; case SPCLKEY | FKEY | F(59): /* page down key */ sc_remove_cutmarking(scp); for (i = 0; i < scp->ysize; i++) if (sc_hist_down_line(scp)) { if (!(flags & SCGETC_CN)) sc_bell(scp, bios_value.bell_pitch, BELL_DURATION); break; } goto next_code; } #endif /* SC_NO_HISTORY */ } /* * Process and consume special keys here. Return a plain char code * or a char code with the META flag or a function key code. */ if (c & RELKEY) { /* key released */ /* goto next_code */ } else { /* key pressed */ if (c & SPCLKEY) { c &= ~SPCLKEY; switch (KEYCHAR(c)) { /* LOCKING KEYS */ case NLK: case CLK: case ALK: break; case SLK: (void)kbdd_ioctl( sc->kbd, KDGKBSTATE, (caddr_t)&f); if (f & SLKED) { scp->status |= SLKED; } else { if (scp->status & SLKED) { scp->status &= ~SLKED; #ifndef SC_NO_HISTORY if (scp->status & BUFFER_SAVED) { if (!sc_hist_restore( scp)) sc_remove_cutmarking( scp); scp->status &= ~BUFFER_SAVED; scp->status |= CURSOR_ENABLED; sc_draw_cursor_image( scp); } /* Only safe in Giant-locked * context. */ tp = SC_DEV(sc, scp->index); if (!(flags & SCGETC_CN) && tty_opened_ns(tp)) sctty_outwakeup(tp); #endif } } break; case PASTE: #ifndef SC_NO_CUTPASTE sc_mouse_paste(scp); #endif break; /* NON-LOCKING KEYS */ case NOP: case LSH: case RSH: case LCTR: case RCTR: case LALT: case RALT: case ASH: case META: break; case BTAB: if (!(sc->flags & SC_SCRN_BLANKED)) return c; break; case SPSC: #ifdef DEV_SPLASH /* force activatation/deactivation of the screen * saver */ if (!(sc->flags & SC_SCRN_BLANKED)) { run_scrn_saver = TRUE; sc->scrn_time_stamp -= scrn_blank_time; } if (cold) { /* * While devices are being probed, the * screen saver need to be invoked * explicitly. XXX */ if (sc->flags & SC_SCRN_BLANKED) { scsplash_stick(FALSE); stop_scrn_saver( sc, current_saver); } else { if (!ISGRAPHSC(scp)) { scsplash_stick(TRUE); (*current_saver)( sc, TRUE); } } } #endif /* DEV_SPLASH */ break; case RBT: #ifndef SC_DISABLE_REBOOT if (enable_reboot && !(flags & SCGETC_CN)) shutdown_nice(0); #endif break; case HALT: #ifndef SC_DISABLE_REBOOT if (enable_reboot && !(flags & SCGETC_CN)) shutdown_nice(RB_HALT); #endif break; case PDWN: #ifndef SC_DISABLE_REBOOT if (enable_reboot && !(flags & SCGETC_CN)) shutdown_nice(RB_HALT | RB_POWEROFF); #endif break; case SUSP: power_pm_suspend(POWER_SLEEP_STATE_SUSPEND); break; case STBY: power_pm_suspend(POWER_SLEEP_STATE_STANDBY); break; case DBG: #ifndef SC_DISABLE_KDBKEY if (enable_kdbkey) kdb_break(); #endif break; case PNC: if (enable_panic_key) panic("Forced by the panic key"); break; case NEXT: this_scr = scp->index; for (i = (this_scr - sc->first_vty + 1) % sc->vtys; sc->first_vty + i != this_scr; i = (i + 1) % sc->vtys) { struct tty *tp = SC_DEV(sc, sc->first_vty + i); if (tty_opened_ns(tp)) { sc_switch_scr( scp->sc, sc->first_vty + i); break; } } break; case PREV: this_scr = scp->index; for (i = (this_scr - sc->first_vty + sc->vtys - 1) % sc->vtys; sc->first_vty + i != this_scr; i = (i + sc->vtys - 1) % sc->vtys) { struct tty *tp = SC_DEV(sc, sc->first_vty + i); if (tty_opened_ns(tp)) { sc_switch_scr( scp->sc, sc->first_vty + i); break; } } break; default: if (KEYCHAR(c) >= F_SCR && KEYCHAR(c) <= L_SCR) { sc_switch_scr(scp->sc, sc->first_vty + KEYCHAR(c) - F_SCR); break; } /* assert(c & FKEY) */ if (!(sc->flags & SC_SCRN_BLANKED)) return c; break; } /* goto next_code */ } else { /* regular keys (maybe MKEY is set) */ #if !defined(SC_DISABLE_KDBKEY) && defined(KDB) if (enable_kdbkey) kdb_alt_break(c, &sc->sc_altbrk); #endif if (!(sc->flags & SC_SCRN_BLANKED)) return c; } } goto next_code; } static int sctty_mmap(struct tty *tp, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { scr_stat *scp; scp = sc_get_stat(tp); if (scp != scp->sc->cur_scp) return -1; return vidd_mmap(scp->sc->adp, offset, paddr, nprot, memattr); } static void update_font(scr_stat *scp) { #ifndef SC_NO_FONT_LOADING /* load appropriate font */ if (!(scp->status & GRAPHICS_MODE)) { if (!(scp->status & PIXEL_MODE) && ISFONTAVAIL(scp->sc->adp->va_flags)) { if (scp->font_size < 14) { if (scp->sc->fonts_loaded & FONT_8) sc_load_font(scp, 0, 8, 8, scp->sc->font_8, 0, 256); } else if (scp->font_size >= 16) { if (scp->sc->fonts_loaded & FONT_16) sc_load_font(scp, 0, 16, 8, scp->sc->font_16, 0, 256); } else { if (scp->sc->fonts_loaded & FONT_14) sc_load_font(scp, 0, 14, 8, scp->sc->font_14, 0, 256); } /* * FONT KLUDGE: * This is an interim kludge to display correct font. * Always use the font page #0 on the video plane 2. * Somehow we cannot show the font in other font pages * on some video cards... XXX */ sc_show_font(scp, 0); } mark_all(scp); } #endif /* !SC_NO_FONT_LOADING */ } static int save_kbd_state(scr_stat *scp) { int state; int error; error = kbdd_ioctl(scp->sc->kbd, KDGKBSTATE, (caddr_t)&state); if (error == ENOIOCTL) error = ENODEV; if (error == 0) { scp->status &= ~LOCK_MASK; scp->status |= state; } return error; } static int update_kbd_state(scr_stat *scp, int new_bits, int mask) { int state; int error; if (mask != LOCK_MASK) { error = kbdd_ioctl(scp->sc->kbd, KDGKBSTATE, (caddr_t)&state); if (error == ENOIOCTL) error = ENODEV; if (error) return error; state &= ~mask; state |= new_bits & mask; } else { state = new_bits & LOCK_MASK; } error = kbdd_ioctl(scp->sc->kbd, KDSKBSTATE, (caddr_t)&state); if (error == ENOIOCTL) error = ENODEV; return error; } static int update_kbd_leds(scr_stat *scp, int which) { int error; which &= LOCK_MASK; error = kbdd_ioctl(scp->sc->kbd, KDSETLED, (caddr_t)&which); if (error == ENOIOCTL) error = ENODEV; return error; } int set_mode(scr_stat *scp) { video_info_t info; /* reject unsupported mode */ if (vidd_get_info(scp->sc->adp, scp->mode, &info)) return 1; /* if this vty is not currently showing, do nothing */ if (scp != scp->sc->cur_scp) return 0; /* setup video hardware for the given mode */ vidd_set_mode(scp->sc->adp, scp->mode); scp->rndr->init(scp); sc_vtb_init(&scp->scr, VTB_FRAMEBUFFER, scp->xsize, scp->ysize, (void *)scp->sc->adp->va_window, FALSE); update_font(scp); sc_set_border(scp, scp->border); sc_set_cursor_image(scp); return 0; } void sc_set_border(scr_stat *scp, int color) { SC_VIDEO_LOCK(scp->sc); (*scp->rndr->draw_border)(scp, color); SC_VIDEO_UNLOCK(scp->sc); } #ifndef SC_NO_FONT_LOADING void sc_load_font(scr_stat *scp, int page, int size, int width, u_char *buf, int base, int count) { sc_softc_t *sc; sc = scp->sc; sc->font_loading_in_progress = TRUE; vidd_load_font(sc->adp, page, size, width, buf, base, count); sc->font_loading_in_progress = FALSE; } void sc_save_font(scr_stat *scp, int page, int size, int width, u_char *buf, int base, int count) { sc_softc_t *sc; sc = scp->sc; sc->font_loading_in_progress = TRUE; vidd_save_font(sc->adp, page, size, width, buf, base, count); sc->font_loading_in_progress = FALSE; } void sc_show_font(scr_stat *scp, int page) { vidd_show_font(scp->sc->adp, page); } #endif /* !SC_NO_FONT_LOADING */ void sc_paste(scr_stat *scp, const u_char *p, int count) { struct tty *tp; u_char *rmap; tp = SC_DEV(scp->sc, scp->sc->cur_scp->index); if (!tty_opened_ns(tp)) return; rmap = scp->sc->scr_rmap; for (; count > 0; --count) ttydisc_rint(tp, rmap[*p++], 0); ttydisc_rint_done(tp); } void sc_respond(scr_stat *scp, const u_char *p, int count, int wakeup) { struct tty *tp; tp = SC_DEV(scp->sc, scp->sc->cur_scp->index); if (!tty_opened_ns(tp)) return; ttydisc_rint_simple(tp, p, count); if (wakeup) { /* XXX: we can't always call ttydisc_rint_done() here! */ ttydisc_rint_done(tp); } } +/* + * pitch is the divisor for 1.193182MHz PIT clock. By dividing 1193172 / pitch, + * we convert it back to Hz. + * duration is in ticks of 1/hz. + */ void sc_bell(scr_stat *scp, int pitch, int duration) { if (cold || kdb_active || shutdown_in_progress || !enable_bell) return; if (scp != scp->sc->cur_scp && (scp->sc->flags & SC_QUIET_BELL)) return; if (scp->sc->flags & SC_VISUAL_BELL) { if (scp->sc->blink_in_progress) return; scp->sc->blink_in_progress = 3; if (scp != scp->sc->cur_scp) scp->sc->blink_in_progress += 2; blink_screen(scp->sc->cur_scp); } else if (duration != 0 && pitch != 0) { if (scp != scp->sc->cur_scp) pitch *= 2; - sysbeep(1193182 / pitch, duration); + sysbeep(1193182 / pitch, SBT_1S * duration / hz); } } static int sc_kattr(void) { if (sc_console == NULL) return (SC_KERNEL_CONS_ATTR); /* for very early, before pcpu */ return (sc_kattrtab[curcpu % nitems(sc_kattrtab)]); } static void blink_screen(void *arg) { scr_stat *scp = arg; struct tty *tp; if (ISGRAPHSC(scp) || (scp->sc->blink_in_progress <= 1)) { scp->sc->blink_in_progress = 0; mark_all(scp); tp = SC_DEV(scp->sc, scp->index); if (tty_opened_ns(tp)) sctty_outwakeup(tp); if (scp->sc->delayed_next_scr) sc_switch_scr(scp->sc, scp->sc->delayed_next_scr - 1); } else { (*scp->rndr->draw)(scp, 0, scp->xsize * scp->ysize, scp->sc->blink_in_progress & 1); scp->sc->blink_in_progress--; callout_reset_sbt(&scp->sc->cblink, SBT_1S / 15, 0, blink_screen, scp, C_PREL(0)); } } /* * Until sc_attach_unit() gets called no dev structures will be available * to store the per-screen current status. This is the case when the * kernel is initially booting and needs access to its console. During * this early phase of booting the console's current status is kept in * one statically defined scr_stat structure, and any pointers to the * dev structures will be NULL. */ static scr_stat * sc_get_stat(struct tty *tp) { if (tp == NULL) return (&main_console); return (SC_STAT(tp)); } /* * Allocate active keyboard. Try to allocate "kbdmux" keyboard first, and, * if found, add all non-busy keyboards to "kbdmux". Otherwise look for * any keyboard. */ static int sc_allocate_keyboard(sc_softc_t *sc, int unit) { int idx0, idx; keyboard_t *k0, *k; keyboard_info_t ki; idx0 = kbd_allocate("kbdmux", -1, (void *)&sc->kbd, sckbdevent, sc); if (idx0 != -1) { k0 = kbd_get_keyboard(idx0); for (idx = kbd_find_keyboard2("*", -1, 0); idx != -1; idx = kbd_find_keyboard2("*", -1, idx + 1)) { k = kbd_get_keyboard(idx); if (idx == idx0 || KBD_IS_BUSY(k)) continue; bzero(&ki, sizeof(ki)); strcpy(ki.kb_name, k->kb_name); ki.kb_unit = k->kb_unit; (void)kbdd_ioctl(k0, KBADDKBD, (caddr_t)&ki); } } else idx0 = kbd_allocate( "*", unit, (void *)&sc->kbd, sckbdevent, sc); return (idx0); } diff --git a/sys/dev/vt/vt_core.c b/sys/dev/vt/vt_core.c index 075b23597f68..567cdd2890d5 100644 --- a/sys/dev/vt/vt_core.c +++ b/sys/dev/vt/vt_core.c @@ -1,3179 +1,3179 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009, 2013 The FreeBSD Foundation * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Portions of this software were developed by Oleksandr Rybalko * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386__) || defined(__amd64__) #include #include #endif static int vtterm_cngrab_noswitch(struct vt_device *, struct vt_window *); static int vtterm_cnungrab_noswitch(struct vt_device *, struct vt_window *); static tc_bell_t vtterm_bell; static tc_cursor_t vtterm_cursor; static tc_putchar_t vtterm_putchar; static tc_fill_t vtterm_fill; static tc_copy_t vtterm_copy; static tc_pre_input_t vtterm_pre_input; static tc_post_input_t vtterm_post_input; static tc_param_t vtterm_param; static tc_done_t vtterm_done; static tc_cnprobe_t vtterm_cnprobe; static tc_cngetc_t vtterm_cngetc; static tc_cngrab_t vtterm_cngrab; static tc_cnungrab_t vtterm_cnungrab; static tc_opened_t vtterm_opened; static tc_ioctl_t vtterm_ioctl; static tc_mmap_t vtterm_mmap; const struct terminal_class vt_termclass = { .tc_bell = vtterm_bell, .tc_cursor = vtterm_cursor, .tc_putchar = vtterm_putchar, .tc_fill = vtterm_fill, .tc_copy = vtterm_copy, .tc_pre_input = vtterm_pre_input, .tc_post_input = vtterm_post_input, .tc_param = vtterm_param, .tc_done = vtterm_done, .tc_cnprobe = vtterm_cnprobe, .tc_cngetc = vtterm_cngetc, .tc_cngrab = vtterm_cngrab, .tc_cnungrab = vtterm_cnungrab, .tc_opened = vtterm_opened, .tc_ioctl = vtterm_ioctl, .tc_mmap = vtterm_mmap, }; /* * Use a constant timer of 25 Hz to redraw the screen. * * XXX: In theory we should only fire up the timer when there is really * activity. Unfortunately we cannot always start timers. We really * don't want to process kernel messages synchronously, because it * really slows down the system. */ #define VT_TIMERFREQ 25 /* Bell pitch/duration. */ -#define VT_BELLDURATION ((5 * hz + 99) / 100) -#define VT_BELLPITCH 800 +#define VT_BELLDURATION (SBT_1S / 20) +#define VT_BELLPITCH (1193182 / 800) /* Approx 1491Hz */ #define VT_UNIT(vw) ((vw)->vw_device->vd_unit * VT_MAXWINDOWS + \ (vw)->vw_number) static SYSCTL_NODE(_kern, OID_AUTO, vt, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "vt(9) parameters"); static VT_SYSCTL_INT(enable_altgr, 1, "Enable AltGr key (Do not assume R.Alt as Alt)"); static VT_SYSCTL_INT(enable_bell, 0, "Enable bell"); static VT_SYSCTL_INT(debug, 0, "vt(9) debug level"); static VT_SYSCTL_INT(deadtimer, 15, "Time to wait busy process in VT_PROCESS mode"); static VT_SYSCTL_INT(suspendswitch, 1, "Switch to VT0 before suspend"); /* Allow to disable some keyboard combinations. */ static VT_SYSCTL_INT(kbd_halt, 1, "Enable halt keyboard combination. " "See kbdmap(5) to configure."); static VT_SYSCTL_INT(kbd_poweroff, 1, "Enable Power Off keyboard combination. " "See kbdmap(5) to configure."); static VT_SYSCTL_INT(kbd_reboot, 1, "Enable reboot keyboard combination. " "See kbdmap(5) to configure (typically Ctrl-Alt-Delete)."); static VT_SYSCTL_INT(kbd_debug, 1, "Enable key combination to enter debugger. " "See kbdmap(5) to configure (typically Ctrl-Alt-Esc)."); static VT_SYSCTL_INT(kbd_panic, 0, "Enable request to panic. " "See kbdmap(5) to configure."); /* Used internally, not a tunable. */ int vt_draw_logo_cpus; VT_SYSCTL_INT(splash_cpu, 0, "Show logo CPUs during boot"); VT_SYSCTL_INT(splash_ncpu, 0, "Override number of logos displayed " "(0 = do not override)"); VT_SYSCTL_INT(splash_cpu_style, 2, "Draw logo style " "(0 = Alternate beastie, 1 = Beastie, 2 = Orb)"); VT_SYSCTL_INT(splash_cpu_duration, 10, "Hide logos after (seconds)"); static unsigned int vt_unit = 0; static MALLOC_DEFINE(M_VT, "vt", "vt device"); struct vt_device *main_vd = &vt_consdev; /* Boot logo. */ extern unsigned int vt_logo_width; extern unsigned int vt_logo_height; extern unsigned int vt_logo_depth; extern unsigned char vt_logo_image[]; #ifndef DEV_SPLASH #define vtterm_draw_cpu_logos(...) do {} while (0) const unsigned int vt_logo_sprite_height; #endif /* * Console font. vt_font_loader will be filled with font data passed * by loader. If there is no font passed by boot loader, we use built in * default. */ extern struct vt_font vt_font_default; static struct vt_font vt_font_loader; static struct vt_font *vt_font_assigned = &vt_font_default; #ifndef SC_NO_CUTPASTE extern struct vt_mouse_cursor vt_default_mouse_pointer; #endif static int signal_vt_rel(struct vt_window *); static int signal_vt_acq(struct vt_window *); static int finish_vt_rel(struct vt_window *, int, int *); static int finish_vt_acq(struct vt_window *); static int vt_window_switch(struct vt_window *); static int vt_late_window_switch(struct vt_window *); static int vt_proc_alive(struct vt_window *); static void vt_resize(struct vt_device *); static void vt_update_static(void *); #ifndef SC_NO_CUTPASTE static void vt_mouse_paste(void); #endif static void vt_suspend_handler(void *priv); static void vt_resume_handler(void *priv); SET_DECLARE(vt_drv_set, struct vt_driver); #define _VTDEFH MAX(100, PIXEL_HEIGHT(VT_FB_MAX_HEIGHT)) #define _VTDEFW MAX(200, PIXEL_WIDTH(VT_FB_MAX_WIDTH)) struct terminal vt_consterm; static struct vt_window vt_conswindow; #ifndef SC_NO_CONSDRAWN static term_char_t vt_consdrawn[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)]; static term_color_t vt_consdrawnfg[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)]; static term_color_t vt_consdrawnbg[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)]; #endif struct vt_device vt_consdev = { .vd_driver = NULL, .vd_softc = NULL, .vd_prev_driver = NULL, .vd_prev_softc = NULL, .vd_flags = VDF_INVALID, .vd_windows = { [VT_CONSWINDOW] = &vt_conswindow, }, .vd_curwindow = &vt_conswindow, .vd_kbstate = 0, #ifndef SC_NO_CUTPASTE .vd_pastebuf = { .vpb_buf = NULL, .vpb_bufsz = 0, .vpb_len = 0 }, .vd_mcursor = &vt_default_mouse_pointer, .vd_mcursor_fg = TC_WHITE, .vd_mcursor_bg = TC_BLACK, #endif #ifndef SC_NO_CONSDRAWN .vd_drawn = vt_consdrawn, .vd_drawnfg = vt_consdrawnfg, .vd_drawnbg = vt_consdrawnbg, #endif }; static term_char_t vt_constextbuf[(_VTDEFW) * (VBF_DEFAULT_HISTORY_SIZE)]; static term_char_t *vt_constextbufrows[VBF_DEFAULT_HISTORY_SIZE]; static struct vt_window vt_conswindow = { .vw_number = VT_CONSWINDOW, .vw_flags = VWF_CONSOLE, .vw_buf = { .vb_buffer = &vt_constextbuf[0], .vb_rows = &vt_constextbufrows[0], .vb_history_size = VBF_DEFAULT_HISTORY_SIZE, .vb_curroffset = 0, .vb_roffset = 0, .vb_flags = VBF_STATIC, .vb_mark_start = {.tp_row = 0, .tp_col = 0,}, .vb_mark_end = {.tp_row = 0, .tp_col = 0,}, .vb_scr_size = { .tp_row = _VTDEFH, .tp_col = _VTDEFW, }, }, .vw_device = &vt_consdev, .vw_terminal = &vt_consterm, .vw_kbdmode = K_XLATE, .vw_grabbed = 0, }; struct terminal vt_consterm = { .tm_class = &vt_termclass, .tm_softc = &vt_conswindow, .tm_flags = TF_CONS, }; static struct consdev vt_consterm_consdev = { .cn_ops = &termcn_cnops, .cn_arg = &vt_consterm, .cn_name = "ttyv0", }; /* Add to set of consoles. */ DATA_SET(cons_set, vt_consterm_consdev); /* * Right after kmem is done to allow early drivers to use locking and allocate * memory. */ SYSINIT(vt_update_static, SI_SUB_KMEM, SI_ORDER_ANY, vt_update_static, &vt_consdev); /* Delay until all devices attached, to not waste time. */ SYSINIT(vt_early_cons, SI_SUB_INT_CONFIG_HOOKS, SI_ORDER_ANY, vt_upgrade, &vt_consdev); /* Initialize locks/mem depended members. */ static void vt_update_static(void *dummy) { if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_driver != NULL) printf("VT(%s): %s %ux%u\n", main_vd->vd_driver->vd_name, (main_vd->vd_flags & VDF_TEXTMODE) ? "text" : "resolution", main_vd->vd_width, main_vd->vd_height); else printf("VT: init without driver.\n"); mtx_init(&main_vd->vd_lock, "vtdev", NULL, MTX_DEF); cv_init(&main_vd->vd_winswitch, "vtwswt"); } static void vt_schedule_flush(struct vt_device *vd, int ms) { if (ms <= 0) /* Default to initial value. */ ms = 1000 / VT_TIMERFREQ; callout_schedule(&vd->vd_timer, hz / (1000 / ms)); } void vt_resume_flush_timer(struct vt_window *vw, int ms) { struct vt_device *vd = vw->vw_device; if (vd->vd_curwindow != vw) return; if (!(vd->vd_flags & VDF_ASYNC) || !atomic_cmpset_int(&vd->vd_timer_armed, 0, 1)) return; vt_schedule_flush(vd, ms); } static void vt_suspend_flush_timer(struct vt_device *vd) { /* * As long as this function is called locked, callout_stop() * has the same effect like callout_drain() with regard to * preventing the callback function from executing. */ VT_LOCK_ASSERT(vd, MA_OWNED); if (!(vd->vd_flags & VDF_ASYNC) || !atomic_cmpset_int(&vd->vd_timer_armed, 1, 0)) return; callout_stop(&vd->vd_timer); } static void vt_switch_timer(void *arg) { (void)vt_late_window_switch((struct vt_window *)arg); } static int vt_save_kbd_mode(struct vt_window *vw, keyboard_t *kbd) { int mode, ret; mode = 0; ret = kbdd_ioctl(kbd, KDGKBMODE, (caddr_t)&mode); if (ret == ENOIOCTL) ret = ENODEV; if (ret != 0) return (ret); vw->vw_kbdmode = mode; return (0); } static int vt_update_kbd_mode(struct vt_window *vw, keyboard_t *kbd) { int ret; ret = kbdd_ioctl(kbd, KDSKBMODE, (caddr_t)&vw->vw_kbdmode); if (ret == ENOIOCTL) ret = ENODEV; return (ret); } static int vt_save_kbd_state(struct vt_window *vw, keyboard_t *kbd) { int state, ret; state = 0; ret = kbdd_ioctl(kbd, KDGKBSTATE, (caddr_t)&state); if (ret == ENOIOCTL) ret = ENODEV; if (ret != 0) return (ret); vw->vw_kbdstate &= ~LOCK_MASK; vw->vw_kbdstate |= state & LOCK_MASK; return (0); } static int vt_update_kbd_state(struct vt_window *vw, keyboard_t *kbd) { int state, ret; state = vw->vw_kbdstate & LOCK_MASK; ret = kbdd_ioctl(kbd, KDSKBSTATE, (caddr_t)&state); if (ret == ENOIOCTL) ret = ENODEV; return (ret); } static int vt_save_kbd_leds(struct vt_window *vw, keyboard_t *kbd) { int leds, ret; leds = 0; ret = kbdd_ioctl(kbd, KDGETLED, (caddr_t)&leds); if (ret == ENOIOCTL) ret = ENODEV; if (ret != 0) return (ret); vw->vw_kbdstate &= ~LED_MASK; vw->vw_kbdstate |= leds & LED_MASK; return (0); } static int vt_update_kbd_leds(struct vt_window *vw, keyboard_t *kbd) { int leds, ret; leds = vw->vw_kbdstate & LED_MASK; ret = kbdd_ioctl(kbd, KDSETLED, (caddr_t)&leds); if (ret == ENOIOCTL) ret = ENODEV; return (ret); } static int vt_window_preswitch(struct vt_window *vw, struct vt_window *curvw) { DPRINTF(40, "%s\n", __func__); curvw->vw_switch_to = vw; /* Set timer to allow switch in case when process hang. */ callout_reset(&vw->vw_proc_dead_timer, hz * vt_deadtimer, vt_switch_timer, (void *)vw); /* Notify process about vt switch attempt. */ DPRINTF(30, "%s: Notify process.\n", __func__); signal_vt_rel(curvw); return (0); } static int vt_window_postswitch(struct vt_window *vw) { signal_vt_acq(vw); return (0); } /* vt_late_window_switch will do VT switching for regular case. */ static int vt_late_window_switch(struct vt_window *vw) { struct vt_window *curvw; int ret; callout_stop(&vw->vw_proc_dead_timer); ret = vt_window_switch(vw); if (ret != 0) { /* * If the switch hasn't happened, then return the VT * to the current owner, if any. */ curvw = vw->vw_device->vd_curwindow; if (curvw->vw_smode.mode == VT_PROCESS) (void)vt_window_postswitch(curvw); return (ret); } /* Notify owner process about terminal availability. */ if (vw->vw_smode.mode == VT_PROCESS) { ret = vt_window_postswitch(vw); } return (ret); } /* Switch window. */ static int vt_proc_window_switch(struct vt_window *vw) { struct vt_window *curvw; struct vt_device *vd; int ret; /* Prevent switching to NULL */ if (vw == NULL) { DPRINTF(30, "%s: Cannot switch: vw is NULL.", __func__); return (EINVAL); } vd = vw->vw_device; curvw = vd->vd_curwindow; /* Check if virtual terminal is locked */ if (curvw->vw_flags & VWF_VTYLOCK) return (EBUSY); /* Check if switch already in progress */ if (curvw->vw_flags & VWF_SWWAIT_REL) { /* Check if switching to same window */ if (curvw->vw_switch_to == vw) { DPRINTF(30, "%s: Switch in progress to same vw.", __func__); return (0); /* success */ } DPRINTF(30, "%s: Switch in progress to different vw.", __func__); return (EBUSY); } /* Avoid switching to already selected window */ if (vw == curvw) { DPRINTF(30, "%s: Cannot switch: vw == curvw.", __func__); return (0); /* success */ } /* * Early check for an attempt to switch to a non-functional VT. * The same check is done in vt_window_switch(), but it's better * to fail as early as possible to avoid needless pre-switch * actions. */ VT_LOCK(vd); if ((vw->vw_flags & (VWF_OPENED|VWF_CONSOLE)) == 0) { VT_UNLOCK(vd); return (EINVAL); } VT_UNLOCK(vd); /* Ask current process permission to switch away. */ if (curvw->vw_smode.mode == VT_PROCESS) { DPRINTF(30, "%s: VT_PROCESS ", __func__); if (vt_proc_alive(curvw) == FALSE) { DPRINTF(30, "Dead. Cleaning."); /* Dead */ } else { DPRINTF(30, "%s: Signaling process.\n", __func__); /* Alive, try to ask him. */ ret = vt_window_preswitch(vw, curvw); /* Wait for process answer or timeout. */ return (ret); } DPRINTF(30, "\n"); } ret = vt_late_window_switch(vw); return (ret); } /* Switch window ignoring process locking. */ static int vt_window_switch(struct vt_window *vw) { struct vt_device *vd = vw->vw_device; struct vt_window *curvw = vd->vd_curwindow; keyboard_t *kbd; if (kdb_active) { /* * When grabbing the console for the debugger, avoid * locks as that can result in deadlock. While this * could use try locks, that wouldn't really make a * difference as there are sufficient barriers in * debugger entry/exit to be equivalent to * successfully try-locking here. */ if (curvw == vw) return (0); if (!(vw->vw_flags & (VWF_OPENED|VWF_CONSOLE))) return (EINVAL); vd->vd_curwindow = vw; vd->vd_flags |= VDF_INVALID; if (vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); return (0); } VT_LOCK(vd); if (curvw == vw) { /* * Nothing to do, except ensure the driver has the opportunity to * switch to console mode when panicking, making sure the panic * is readable (even when a GUI was using ttyv0). */ if ((kdb_active || panicstr) && vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); VT_UNLOCK(vd); return (0); } if (!(vw->vw_flags & (VWF_OPENED|VWF_CONSOLE))) { VT_UNLOCK(vd); return (EINVAL); } vt_suspend_flush_timer(vd); vd->vd_curwindow = vw; vd->vd_flags |= VDF_INVALID; cv_broadcast(&vd->vd_winswitch); VT_UNLOCK(vd); if (vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); vt_resume_flush_timer(vw, 0); /* Restore per-window keyboard mode. */ mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) { if (curvw->vw_kbdmode == K_XLATE) vt_save_kbd_state(curvw, kbd); vt_update_kbd_mode(vw, kbd); vt_update_kbd_state(vw, kbd); } mtx_unlock(&Giant); DPRINTF(10, "%s(ttyv%d) done\n", __func__, vw->vw_number); return (0); } void vt_termsize(struct vt_device *vd, struct vt_font *vf, term_pos_t *size) { size->tp_row = vd->vd_height; if (vt_draw_logo_cpus) size->tp_row -= vt_logo_sprite_height; size->tp_col = vd->vd_width; if (vf != NULL) { size->tp_row = MIN(size->tp_row / vf->vf_height, PIXEL_HEIGHT(VT_FB_MAX_HEIGHT)); size->tp_col = MIN(size->tp_col / vf->vf_width, PIXEL_WIDTH(VT_FB_MAX_WIDTH)); } } static inline void vt_termrect(struct vt_device *vd, struct vt_font *vf, term_rect_t *rect) { rect->tr_begin.tp_row = rect->tr_begin.tp_col = 0; if (vt_draw_logo_cpus) rect->tr_begin.tp_row = vt_logo_sprite_height; rect->tr_end.tp_row = vd->vd_height; rect->tr_end.tp_col = vd->vd_width; if (vf != NULL) { rect->tr_begin.tp_row = howmany(rect->tr_begin.tp_row, vf->vf_height); rect->tr_end.tp_row = MIN(rect->tr_end.tp_row / vf->vf_height, PIXEL_HEIGHT(VT_FB_MAX_HEIGHT)); rect->tr_end.tp_col = MIN(rect->tr_end.tp_col / vf->vf_width, PIXEL_WIDTH(VT_FB_MAX_WIDTH)); } } void vt_winsize(struct vt_device *vd, struct vt_font *vf, struct winsize *size) { size->ws_ypixel = vd->vd_height; if (vt_draw_logo_cpus) size->ws_ypixel -= vt_logo_sprite_height; size->ws_row = size->ws_ypixel; size->ws_col = size->ws_xpixel = vd->vd_width; if (vf != NULL) { size->ws_row = MIN(size->ws_row / vf->vf_height, PIXEL_HEIGHT(VT_FB_MAX_HEIGHT)); size->ws_col = MIN(size->ws_col / vf->vf_width, PIXEL_WIDTH(VT_FB_MAX_WIDTH)); } } void vt_compute_drawable_area(struct vt_window *vw) { struct vt_device *vd; struct vt_font *vf; vt_axis_t height; vd = vw->vw_device; if (vw->vw_font == NULL) { vw->vw_draw_area.tr_begin.tp_col = 0; vw->vw_draw_area.tr_begin.tp_row = 0; if (vt_draw_logo_cpus) vw->vw_draw_area.tr_begin.tp_row = vt_logo_sprite_height; vw->vw_draw_area.tr_end.tp_col = vd->vd_width; vw->vw_draw_area.tr_end.tp_row = vd->vd_height; return; } vf = vw->vw_font; /* * Compute the drawable area, so that the text is centered on * the screen. */ height = vd->vd_height; if (vt_draw_logo_cpus) height -= vt_logo_sprite_height; vw->vw_draw_area.tr_begin.tp_col = (vd->vd_width % vf->vf_width) / 2; vw->vw_draw_area.tr_begin.tp_row = (height % vf->vf_height) / 2; if (vt_draw_logo_cpus) vw->vw_draw_area.tr_begin.tp_row += vt_logo_sprite_height; vw->vw_draw_area.tr_end.tp_col = vw->vw_draw_area.tr_begin.tp_col + rounddown(vd->vd_width, vf->vf_width); vw->vw_draw_area.tr_end.tp_row = vw->vw_draw_area.tr_begin.tp_row + rounddown(height, vf->vf_height); } static void vt_scroll(struct vt_window *vw, int offset, int whence) { int diff; term_pos_t size; if ((vw->vw_flags & VWF_SCROLL) == 0) return; vt_termsize(vw->vw_device, vw->vw_font, &size); diff = vthistory_seek(&vw->vw_buf, offset, whence); if (diff) vw->vw_device->vd_flags |= VDF_INVALID; vt_resume_flush_timer(vw, 0); } static int vt_machine_kbdevent(struct vt_device *vd, int c) { switch (c) { case SPCLKEY | DBG: /* kbdmap(5) keyword `debug`. */ if (vt_kbd_debug) { kdb_enter(KDB_WHY_BREAK, "manual escape to debugger"); #if VT_ALT_TO_ESC_HACK /* * There's an unfortunate conflict between SPCLKEY|DBG * and VT_ALT_TO_ESC_HACK. Just assume they didn't mean * it if we got to here. */ vd->vd_kbstate &= ~ALKED; #endif } return (1); case SPCLKEY | HALT: /* kbdmap(5) keyword `halt`. */ if (vt_kbd_halt) shutdown_nice(RB_HALT); return (1); case SPCLKEY | PASTE: /* kbdmap(5) keyword `paste`. */ #ifndef SC_NO_CUTPASTE /* Insert text from cut-paste buffer. */ vt_mouse_paste(); #endif break; case SPCLKEY | PDWN: /* kbdmap(5) keyword `pdwn`. */ if (vt_kbd_poweroff) shutdown_nice(RB_HALT|RB_POWEROFF); return (1); case SPCLKEY | PNC: /* kbdmap(5) keyword `panic`. */ /* * Request to immediate panic if sysctl * kern.vt.enable_panic_key allow it. */ if (vt_kbd_panic) panic("Forced by the panic key"); return (1); case SPCLKEY | RBT: /* kbdmap(5) keyword `boot`. */ if (vt_kbd_reboot) shutdown_nice(RB_AUTOBOOT); return (1); case SPCLKEY | SPSC: /* kbdmap(5) keyword `spsc`. */ /* Force activatation/deactivation of the screen saver. */ /* TODO */ return (1); case SPCLKEY | STBY: /* XXX Not present in kbdcontrol parser. */ /* Put machine into Stand-By mode. */ power_pm_suspend(POWER_SLEEP_STATE_STANDBY); return (1); case SPCLKEY | SUSP: /* kbdmap(5) keyword `susp`. */ /* Suspend machine. */ power_pm_suspend(POWER_SLEEP_STATE_SUSPEND); return (1); } return (0); } static void vt_scrollmode_kbdevent(struct vt_window *vw, int c, int console) { struct vt_device *vd; term_pos_t size; vd = vw->vw_device; /* Only special keys handled in ScrollLock mode */ if ((c & SPCLKEY) == 0) return; c &= ~SPCLKEY; if (console == 0) { if (c >= F_SCR && c <= MIN(L_SCR, F_SCR + VT_MAXWINDOWS - 1)) { vw = vd->vd_windows[c - F_SCR]; vt_proc_window_switch(vw); return; } VT_LOCK(vd); } switch (c) { case SLK: { /* Turn scrolling off. */ vt_scroll(vw, 0, VHS_END); VTBUF_SLCK_DISABLE(&vw->vw_buf); vw->vw_flags &= ~VWF_SCROLL; break; } case FKEY | F(49): /* Home key. */ vt_scroll(vw, 0, VHS_SET); break; case FKEY | F(50): /* Arrow up. */ vt_scroll(vw, -1, VHS_CUR); break; case FKEY | F(51): /* Page up. */ vt_termsize(vd, vw->vw_font, &size); vt_scroll(vw, -size.tp_row, VHS_CUR); break; case FKEY | F(57): /* End key. */ vt_scroll(vw, 0, VHS_END); break; case FKEY | F(58): /* Arrow down. */ vt_scroll(vw, 1, VHS_CUR); break; case FKEY | F(59): /* Page down. */ vt_termsize(vd, vw->vw_font, &size); vt_scroll(vw, size.tp_row, VHS_CUR); break; } if (console == 0) VT_UNLOCK(vd); } static int vt_processkey(keyboard_t *kbd, struct vt_device *vd, int c) { struct vt_window *vw = vd->vd_curwindow; random_harvest_queue(&c, sizeof(c), RANDOM_KEYBOARD); #if VT_ALT_TO_ESC_HACK if (c & RELKEY) { switch (c & ~RELKEY) { case (SPCLKEY | RALT): if (vt_enable_altgr != 0) break; case (SPCLKEY | LALT): vd->vd_kbstate &= ~ALKED; } /* Other keys ignored for RELKEY event. */ return (0); } else { switch (c & ~RELKEY) { case (SPCLKEY | RALT): if (vt_enable_altgr != 0) break; case (SPCLKEY | LALT): vd->vd_kbstate |= ALKED; } } #else if (c & RELKEY) /* Other keys ignored for RELKEY event. */ return (0); #endif if (vt_machine_kbdevent(vd, c)) return (0); if (vw->vw_flags & VWF_SCROLL) { vt_scrollmode_kbdevent(vw, c, 0/* Not a console */); /* Scroll mode keys handled, nothing to do more. */ return (0); } if (c & SPCLKEY) { c &= ~SPCLKEY; if (c >= F_SCR && c <= MIN(L_SCR, F_SCR + VT_MAXWINDOWS - 1)) { vw = vd->vd_windows[c - F_SCR]; vt_proc_window_switch(vw); return (0); } switch (c) { case NEXT: /* Switch to next VT. */ c = (vw->vw_number + 1) % VT_MAXWINDOWS; vw = vd->vd_windows[c]; vt_proc_window_switch(vw); return (0); case PREV: /* Switch to previous VT. */ c = (vw->vw_number + VT_MAXWINDOWS - 1) % VT_MAXWINDOWS; vw = vd->vd_windows[c]; vt_proc_window_switch(vw); return (0); case SLK: { vt_save_kbd_state(vw, kbd); VT_LOCK(vd); if (vw->vw_kbdstate & SLKED) { /* Turn scrolling on. */ vw->vw_flags |= VWF_SCROLL; VTBUF_SLCK_ENABLE(&vw->vw_buf); } else { /* Turn scrolling off. */ vw->vw_flags &= ~VWF_SCROLL; VTBUF_SLCK_DISABLE(&vw->vw_buf); vt_scroll(vw, 0, VHS_END); } VT_UNLOCK(vd); break; } case FKEY | F(1): case FKEY | F(2): case FKEY | F(3): case FKEY | F(4): case FKEY | F(5): case FKEY | F(6): case FKEY | F(7): case FKEY | F(8): case FKEY | F(9): case FKEY | F(10): case FKEY | F(11): case FKEY | F(12): /* F1 through F12 keys. */ terminal_input_special(vw->vw_terminal, TKEY_F1 + c - (FKEY | F(1))); break; case FKEY | F(49): /* Home key. */ terminal_input_special(vw->vw_terminal, TKEY_HOME); break; case FKEY | F(50): /* Arrow up. */ terminal_input_special(vw->vw_terminal, TKEY_UP); break; case FKEY | F(51): /* Page up. */ terminal_input_special(vw->vw_terminal, TKEY_PAGE_UP); break; case FKEY | F(53): /* Arrow left. */ terminal_input_special(vw->vw_terminal, TKEY_LEFT); break; case FKEY | F(55): /* Arrow right. */ terminal_input_special(vw->vw_terminal, TKEY_RIGHT); break; case FKEY | F(57): /* End key. */ terminal_input_special(vw->vw_terminal, TKEY_END); break; case FKEY | F(58): /* Arrow down. */ terminal_input_special(vw->vw_terminal, TKEY_DOWN); break; case FKEY | F(59): /* Page down. */ terminal_input_special(vw->vw_terminal, TKEY_PAGE_DOWN); break; case FKEY | F(60): /* Insert key. */ terminal_input_special(vw->vw_terminal, TKEY_INSERT); break; case FKEY | F(61): /* Delete key. */ terminal_input_special(vw->vw_terminal, TKEY_DELETE); break; } } else if (KEYFLAGS(c) == 0) { /* Don't do UTF-8 conversion when doing raw mode. */ if (vw->vw_kbdmode == K_XLATE) { #if VT_ALT_TO_ESC_HACK if (vd->vd_kbstate & ALKED) { /* * Prepend ESC sequence if one of ALT keys down. */ terminal_input_char(vw->vw_terminal, 0x1b); } #endif #if defined(KDB) kdb_alt_break(c, &vd->vd_altbrk); #endif terminal_input_char(vw->vw_terminal, KEYCHAR(c)); } else terminal_input_raw(vw->vw_terminal, c); } return (0); } static int vt_kbdevent(keyboard_t *kbd, int event, void *arg) { struct vt_device *vd = arg; int c; switch (event) { case KBDIO_KEYINPUT: break; case KBDIO_UNLOADING: mtx_lock(&Giant); vd->vd_keyboard = NULL; kbd_release(kbd, (void *)vd); mtx_unlock(&Giant); return (0); default: return (EINVAL); } while ((c = kbdd_read_char(kbd, 0)) != NOKEY) vt_processkey(kbd, vd, c); return (0); } static int vt_allocate_keyboard(struct vt_device *vd) { int grabbed, i, idx0, idx; keyboard_t *k0, *k; keyboard_info_t ki; /* * If vt_upgrade() happens while the console is grabbed, we are * potentially going to switch keyboard devices while the keyboard is in * use. Unwind the grabbing of the current keyboard first, then we will * re-grab the new keyboard below, before we return. */ if (vd->vd_curwindow == &vt_conswindow) { grabbed = vd->vd_curwindow->vw_grabbed; for (i = 0; i < grabbed; ++i) vtterm_cnungrab_noswitch(vd, vd->vd_curwindow); } idx0 = kbd_allocate("kbdmux", -1, vd, vt_kbdevent, vd); if (idx0 >= 0) { DPRINTF(20, "%s: kbdmux allocated, idx = %d\n", __func__, idx0); k0 = kbd_get_keyboard(idx0); for (idx = kbd_find_keyboard2("*", -1, 0); idx != -1; idx = kbd_find_keyboard2("*", -1, idx + 1)) { k = kbd_get_keyboard(idx); if (idx == idx0 || KBD_IS_BUSY(k)) continue; bzero(&ki, sizeof(ki)); strncpy(ki.kb_name, k->kb_name, sizeof(ki.kb_name)); ki.kb_name[sizeof(ki.kb_name) - 1] = '\0'; ki.kb_unit = k->kb_unit; kbdd_ioctl(k0, KBADDKBD, (caddr_t) &ki); } } else { DPRINTF(20, "%s: no kbdmux allocated\n", __func__); idx0 = kbd_allocate("*", -1, vd, vt_kbdevent, vd); if (idx0 < 0) { DPRINTF(10, "%s: No keyboard found.\n", __func__); return (-1); } k0 = kbd_get_keyboard(idx0); } vd->vd_keyboard = k0; DPRINTF(20, "%s: vd_keyboard = %d\n", __func__, vd->vd_keyboard->kb_index); if (vd->vd_curwindow == &vt_conswindow) { for (i = 0; i < grabbed; ++i) vtterm_cngrab_noswitch(vd, vd->vd_curwindow); } return (idx0); } static void vtterm_bell(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; if (!vt_enable_bell) return; if (vd->vd_flags & VDF_QUIET_BELL) return; - sysbeep(1193182 / VT_BELLPITCH, VT_BELLDURATION); + sysbeep(VT_BELLPITCH, VT_BELLDURATION); } static void vtterm_beep(struct terminal *tm, u_int param) { u_int freq, period; if (!vt_enable_bell) return; if ((param == 0) || ((param & 0xffff) == 0)) { vtterm_bell(tm); return; } - period = ((param >> 16) & 0xffff) * hz / 1000; + period = ((param >> 16) & 0xffff) * SBT_1MS; freq = 1193182 / (param & 0xffff); sysbeep(freq, period); } static void vtterm_cursor(struct terminal *tm, const term_pos_t *p) { struct vt_window *vw = tm->tm_softc; vtbuf_cursor_position(&vw->vw_buf, p); } static void vtterm_putchar(struct terminal *tm, const term_pos_t *p, term_char_t c) { struct vt_window *vw = tm->tm_softc; vtbuf_putchar(&vw->vw_buf, p, c); } static void vtterm_fill(struct terminal *tm, const term_rect_t *r, term_char_t c) { struct vt_window *vw = tm->tm_softc; vtbuf_fill(&vw->vw_buf, r, c); } static void vtterm_copy(struct terminal *tm, const term_rect_t *r, const term_pos_t *p) { struct vt_window *vw = tm->tm_softc; vtbuf_copy(&vw->vw_buf, r, p); } static void vtterm_param(struct terminal *tm, int cmd, unsigned int arg) { struct vt_window *vw = tm->tm_softc; switch (cmd) { case TP_SETLOCALCURSOR: /* * 0 means normal (usually block), 1 means hidden, and * 2 means blinking (always block) for compatibility with * syscons. We don't support any changes except hiding, * so must map 2 to 0. */ arg = (arg == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: vtbuf_cursor_visibility(&vw->vw_buf, arg); vt_resume_flush_timer(vw, 0); break; case TP_MOUSE: vw->vw_mouse_level = arg; break; } } void vt_determine_colors(term_char_t c, int cursor, term_color_t *fg, term_color_t *bg) { term_color_t tmp; int invert; invert = 0; *fg = TCHAR_FGCOLOR(c); if (TCHAR_FORMAT(c) & TF_BOLD) *fg = TCOLOR_LIGHT(*fg); *bg = TCHAR_BGCOLOR(c); if (TCHAR_FORMAT(c) & TF_BLINK) *bg = TCOLOR_LIGHT(*bg); if (TCHAR_FORMAT(c) & TF_REVERSE) invert ^= 1; if (cursor) invert ^= 1; if (invert) { tmp = *fg; *fg = *bg; *bg = tmp; } } #ifndef SC_NO_CUTPASTE int vt_is_cursor_in_area(const struct vt_device *vd, const term_rect_t *area) { unsigned int mx, my; /* * We use the cursor position saved during the current refresh, * in case the cursor moved since. */ mx = vd->vd_mx_drawn + vd->vd_curwindow->vw_draw_area.tr_begin.tp_col; my = vd->vd_my_drawn + vd->vd_curwindow->vw_draw_area.tr_begin.tp_row; if (mx >= area->tr_end.tp_col || mx + vd->vd_mcursor->width <= area->tr_begin.tp_col || my >= area->tr_end.tp_row || my + vd->vd_mcursor->height <= area->tr_begin.tp_row) return (0); return (1); } static void vt_mark_mouse_position_as_dirty(struct vt_device *vd, int locked) { term_rect_t area; struct vt_window *vw; struct vt_font *vf; int x, y; vw = vd->vd_curwindow; vf = vw->vw_font; x = vd->vd_mx_drawn; y = vd->vd_my_drawn; if (vf != NULL) { area.tr_begin.tp_col = x / vf->vf_width; area.tr_begin.tp_row = y / vf->vf_height; area.tr_end.tp_col = ((x + vd->vd_mcursor->width) / vf->vf_width) + 1; area.tr_end.tp_row = ((y + vd->vd_mcursor->height) / vf->vf_height) + 1; } else { /* * No font loaded (ie. vt_vga operating in textmode). * * FIXME: This fake area needs to be revisited once the * mouse cursor is supported in vt_vga's textmode. */ area.tr_begin.tp_col = x; area.tr_begin.tp_row = y; area.tr_end.tp_col = x + 2; area.tr_end.tp_row = y + 2; } if (!locked) vtbuf_lock(&vw->vw_buf); if (vd->vd_driver->vd_invalidate_text) vd->vd_driver->vd_invalidate_text(vd, &area); vtbuf_dirty(&vw->vw_buf, &area); if (!locked) vtbuf_unlock(&vw->vw_buf); } #endif static void vt_set_border(struct vt_device *vd, const term_rect_t *area, term_color_t c) { vd_drawrect_t *drawrect = vd->vd_driver->vd_drawrect; if (drawrect == NULL) return; /* Top bar */ if (area->tr_begin.tp_row > 0) drawrect(vd, 0, 0, vd->vd_width - 1, area->tr_begin.tp_row - 1, 1, c); /* Left bar */ if (area->tr_begin.tp_col > 0) drawrect(vd, 0, area->tr_begin.tp_row, area->tr_begin.tp_col - 1, area->tr_end.tp_row - 1, 1, c); /* Right bar */ if (area->tr_end.tp_col < vd->vd_width) drawrect(vd, area->tr_end.tp_col, area->tr_begin.tp_row, vd->vd_width - 1, area->tr_end.tp_row - 1, 1, c); /* Bottom bar */ if (area->tr_end.tp_row < vd->vd_height) drawrect(vd, 0, area->tr_end.tp_row, vd->vd_width - 1, vd->vd_height - 1, 1, c); } static int vt_flush(struct vt_device *vd) { struct vt_window *vw; struct vt_font *vf; term_rect_t tarea; #ifndef SC_NO_CUTPASTE int cursor_was_shown, cursor_moved; #endif vw = vd->vd_curwindow; if (vw == NULL) return (0); if (vd->vd_flags & VDF_SPLASH || vw->vw_flags & VWF_BUSY) return (0); vf = vw->vw_font; if (((vd->vd_flags & VDF_TEXTMODE) == 0) && (vf == NULL)) return (0); vtbuf_lock(&vw->vw_buf); #ifndef SC_NO_CUTPASTE cursor_was_shown = vd->vd_mshown; cursor_moved = (vd->vd_mx != vd->vd_mx_drawn || vd->vd_my != vd->vd_my_drawn); /* Check if the cursor should be displayed or not. */ if ((vd->vd_flags & VDF_MOUSECURSOR) && /* Mouse support enabled. */ !(vw->vw_flags & VWF_MOUSE_HIDE) && /* Cursor displayed. */ !kdb_active && !KERNEL_PANICKED()) { /* DDB inactive. */ vd->vd_mshown = 1; } else { vd->vd_mshown = 0; } /* * If the cursor changed display state or moved, we must mark * the old position as dirty, so that it's erased. */ if (cursor_was_shown != vd->vd_mshown || (vd->vd_mshown && cursor_moved)) vt_mark_mouse_position_as_dirty(vd, true); /* * Save position of the mouse cursor. It's used by backends to * know where to draw the cursor and during the next refresh to * erase the previous position. */ vd->vd_mx_drawn = vd->vd_mx; vd->vd_my_drawn = vd->vd_my; /* * If the cursor is displayed and has moved since last refresh, * mark the new position as dirty. */ if (vd->vd_mshown && cursor_moved) vt_mark_mouse_position_as_dirty(vd, true); #endif vtbuf_undirty(&vw->vw_buf, &tarea); /* Force a full redraw when the screen contents might be invalid. */ if (vd->vd_flags & (VDF_INVALID | VDF_SUSPENDED)) { const teken_attr_t *a; vd->vd_flags &= ~VDF_INVALID; a = teken_get_curattr(&vw->vw_terminal->tm_emulator); vt_set_border(vd, &vw->vw_draw_area, a->ta_bgcolor); vt_termrect(vd, vf, &tarea); if (vd->vd_driver->vd_invalidate_text) vd->vd_driver->vd_invalidate_text(vd, &tarea); if (vt_draw_logo_cpus) vtterm_draw_cpu_logos(vd); } if (tarea.tr_begin.tp_col < tarea.tr_end.tp_col) { vd->vd_driver->vd_bitblt_text(vd, vw, &tarea); vtbuf_unlock(&vw->vw_buf); return (1); } vtbuf_unlock(&vw->vw_buf); return (0); } static void vt_timer(void *arg) { struct vt_device *vd; int changed; vd = arg; /* Update screen if required. */ changed = vt_flush(vd); /* Schedule for next update. */ if (changed) vt_schedule_flush(vd, 0); else vd->vd_timer_armed = 0; } static void vtterm_pre_input(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; vtbuf_lock(&vw->vw_buf); } static void vtterm_post_input(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; vtbuf_unlock(&vw->vw_buf); vt_resume_flush_timer(vw, 0); } static void vtterm_done(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; if (kdb_active || KERNEL_PANICKED()) { /* Switch to the debugger. */ if (vd->vd_curwindow != vw) { vd->vd_curwindow = vw; vd->vd_flags |= VDF_INVALID; if (vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); } vd->vd_flags &= ~VDF_SPLASH; vt_flush(vd); } else if (!(vd->vd_flags & VDF_ASYNC)) { vt_flush(vd); } } #ifdef DEV_SPLASH static void vtterm_splash(struct vt_device *vd) { vt_axis_t top, left; /* Display a nice boot splash. */ if (!(vd->vd_flags & VDF_TEXTMODE) && (boothowto & RB_MUTE)) { top = (vd->vd_height - vt_logo_height) / 2; left = (vd->vd_width - vt_logo_width) / 2; switch (vt_logo_depth) { case 1: /* XXX: Unhardcode colors! */ vd->vd_driver->vd_bitblt_bmp(vd, vd->vd_curwindow, vt_logo_image, NULL, vt_logo_width, vt_logo_height, left, top, TC_WHITE, TC_BLACK); } vd->vd_flags |= VDF_SPLASH; } } #endif static struct vt_font * parse_font_info_static(struct font_info *fi) { struct vt_font *vfp; uintptr_t ptr; uint32_t checksum; if (fi == NULL) return (NULL); ptr = (uintptr_t)fi; /* * Compute and verify checksum. The total sum of all the fields * must be 0. */ checksum = fi->fi_width; checksum += fi->fi_height; checksum += fi->fi_bitmap_size; for (unsigned i = 0; i < VFNT_MAPS; i++) checksum += fi->fi_map_count[i]; if (checksum + fi->fi_checksum != 0) return (NULL); ptr += sizeof(struct font_info); ptr = roundup2(ptr, 8); vfp = &vt_font_loader; vfp->vf_height = fi->fi_height; vfp->vf_width = fi->fi_width; /* This is default font, set refcount 1 to disable removal. */ vfp->vf_refcount = 1; for (unsigned i = 0; i < VFNT_MAPS; i++) { if (fi->fi_map_count[i] == 0) continue; vfp->vf_map_count[i] = fi->fi_map_count[i]; vfp->vf_map[i] = (vfnt_map_t *)ptr; ptr += (fi->fi_map_count[i] * sizeof(vfnt_map_t)); ptr = roundup2(ptr, 8); } vfp->vf_bytes = (uint8_t *)ptr; return (vfp); } /* * Set up default font with allocated data structures. * However, we can not set refcount here, because it is already set and * incremented in vtterm_cnprobe() to avoid being released by font load from * userland. */ static struct vt_font * parse_font_info(struct font_info *fi) { struct vt_font *vfp; uintptr_t ptr; uint32_t checksum; size_t size; if (fi == NULL) return (NULL); ptr = (uintptr_t)fi; /* * Compute and verify checksum. The total sum of all the fields * must be 0. */ checksum = fi->fi_width; checksum += fi->fi_height; checksum += fi->fi_bitmap_size; for (unsigned i = 0; i < VFNT_MAPS; i++) checksum += fi->fi_map_count[i]; if (checksum + fi->fi_checksum != 0) return (NULL); ptr += sizeof(struct font_info); ptr = roundup2(ptr, 8); vfp = &vt_font_loader; vfp->vf_height = fi->fi_height; vfp->vf_width = fi->fi_width; for (unsigned i = 0; i < VFNT_MAPS; i++) { if (fi->fi_map_count[i] == 0) continue; vfp->vf_map_count[i] = fi->fi_map_count[i]; size = fi->fi_map_count[i] * sizeof(vfnt_map_t); vfp->vf_map[i] = malloc(size, M_VT, M_WAITOK | M_ZERO); bcopy((vfnt_map_t *)ptr, vfp->vf_map[i], size); ptr += size; ptr = roundup2(ptr, 8); } vfp->vf_bytes = malloc(fi->fi_bitmap_size, M_VT, M_WAITOK | M_ZERO); bcopy((uint8_t *)ptr, vfp->vf_bytes, fi->fi_bitmap_size); return (vfp); } static void vt_init_font(void *arg) { caddr_t kmdp; struct font_info *fi; struct vt_font *font; kmdp = preload_search_by_type("elf kernel"); if (kmdp == NULL) kmdp = preload_search_by_type("elf64 kernel"); fi = MD_FETCH(kmdp, MODINFOMD_FONT, struct font_info *); font = parse_font_info(fi); if (font != NULL) vt_font_assigned = font; } SYSINIT(vt_init_font, SI_SUB_KMEM, SI_ORDER_ANY, vt_init_font, &vt_consdev); static void vt_init_font_static(void) { caddr_t kmdp; struct font_info *fi; struct vt_font *font; kmdp = preload_search_by_type("elf kernel"); if (kmdp == NULL) kmdp = preload_search_by_type("elf64 kernel"); fi = MD_FETCH(kmdp, MODINFOMD_FONT, struct font_info *); font = parse_font_info_static(fi); if (font != NULL) vt_font_assigned = font; } static void vtterm_cnprobe(struct terminal *tm, struct consdev *cp) { struct vt_driver *vtd, **vtdlist, *vtdbest = NULL; struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; struct winsize wsz; const term_attr_t *a; if (!vty_enabled(VTY_VT)) return; if (vd->vd_flags & VDF_INITIALIZED) /* Initialization already done. */ return; SET_FOREACH(vtdlist, vt_drv_set) { vtd = *vtdlist; if (vtd->vd_probe == NULL) continue; if (vtd->vd_probe(vd) == CN_DEAD) continue; if ((vtdbest == NULL) || (vtd->vd_priority > vtdbest->vd_priority)) vtdbest = vtd; } if (vtdbest == NULL) { cp->cn_pri = CN_DEAD; vd->vd_flags |= VDF_DEAD; } else { vd->vd_driver = vtdbest; cp->cn_pri = vd->vd_driver->vd_init(vd); } /* Check if driver's vt_init return CN_DEAD. */ if (cp->cn_pri == CN_DEAD) { vd->vd_flags |= VDF_DEAD; } /* Initialize any early-boot keyboard drivers */ kbd_configure(KB_CONF_PROBE_ONLY); vd->vd_unit = atomic_fetchadd_int(&vt_unit, 1); vd->vd_windows[VT_CONSWINDOW] = vw; sprintf(cp->cn_name, "ttyv%r", VT_UNIT(vw)); vt_init_font_static(); /* Attach default font if not in TEXTMODE. */ if ((vd->vd_flags & VDF_TEXTMODE) == 0) { vw->vw_font = vtfont_ref(vt_font_assigned); vt_compute_drawable_area(vw); } /* * The original screen size was faked (_VTDEFW x _VTDEFH). Now * that we have the real viewable size, fix it in the static * buffer. */ if (vd->vd_width != 0 && vd->vd_height != 0) vt_termsize(vd, vw->vw_font, &vw->vw_buf.vb_scr_size); /* We need to access terminal attributes from vtbuf */ vw->vw_buf.vb_terminal = tm; vtbuf_init_early(&vw->vw_buf); vt_winsize(vd, vw->vw_font, &wsz); a = teken_get_curattr(&tm->tm_emulator); terminal_set_winsize_blank(tm, &wsz, 1, a); if (vtdbest != NULL) { #ifdef DEV_SPLASH if (!vt_splash_cpu) vtterm_splash(vd); #endif vd->vd_flags |= VDF_INITIALIZED; } } static int vtterm_cngetc(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; keyboard_t *kbd; u_int c; if (vw->vw_kbdsq && *vw->vw_kbdsq) return (*vw->vw_kbdsq++); /* Make sure the splash screen is not there. */ if (vd->vd_flags & VDF_SPLASH) { /* Remove splash */ vd->vd_flags &= ~VDF_SPLASH; /* Mark screen as invalid to force update */ vd->vd_flags |= VDF_INVALID; vt_flush(vd); } /* Stripped down keyboard handler. */ if ((kbd = vd->vd_keyboard) == NULL) return (-1); /* Force keyboard input mode to K_XLATE */ vw->vw_kbdmode = K_XLATE; vt_update_kbd_mode(vw, kbd); /* Switch the keyboard to polling to make it work here. */ kbdd_poll(kbd, TRUE); c = kbdd_read_char(kbd, 0); kbdd_poll(kbd, FALSE); if (c & RELKEY) return (-1); if (vw->vw_flags & VWF_SCROLL) { vt_scrollmode_kbdevent(vw, c, 1/* Console mode */); vt_flush(vd); return (-1); } /* Stripped down handling of vt_kbdevent(), without locking, etc. */ if (c & SPCLKEY) { switch (c) { case SPCLKEY | SLK: vt_save_kbd_state(vw, kbd); if (vw->vw_kbdstate & SLKED) { /* Turn scrolling on. */ vw->vw_flags |= VWF_SCROLL; VTBUF_SLCK_ENABLE(&vw->vw_buf); } else { /* Turn scrolling off. */ vt_scroll(vw, 0, VHS_END); vw->vw_flags &= ~VWF_SCROLL; VTBUF_SLCK_DISABLE(&vw->vw_buf); } break; /* XXX: KDB can handle history. */ case SPCLKEY | FKEY | F(50): /* Arrow up. */ vw->vw_kbdsq = "\x1b[A"; break; case SPCLKEY | FKEY | F(58): /* Arrow down. */ vw->vw_kbdsq = "\x1b[B"; break; case SPCLKEY | FKEY | F(55): /* Arrow right. */ vw->vw_kbdsq = "\x1b[C"; break; case SPCLKEY | FKEY | F(53): /* Arrow left. */ vw->vw_kbdsq = "\x1b[D"; break; } /* Force refresh to make scrollback work. */ vt_flush(vd); } else if (KEYFLAGS(c) == 0) { return (KEYCHAR(c)); } if (vw->vw_kbdsq && *vw->vw_kbdsq) return (*vw->vw_kbdsq++); return (-1); } /* * These two do most of what we want to do in vtterm_cnungrab, but without * actually switching windows. This is necessary for, e.g., * vt_allocate_keyboard() to get the current keyboard into the state it needs to * be in without damaging the device's window state. * * Both return the current grab count, though it's only used in vtterm_cnungrab. */ static int vtterm_cngrab_noswitch(struct vt_device *vd, struct vt_window *vw) { keyboard_t *kbd; if (vw->vw_grabbed++ > 0) return (vw->vw_grabbed); if ((kbd = vd->vd_keyboard) == NULL) return (1); /* * Make sure the keyboard is accessible even when the kbd device * driver is disabled. */ kbdd_enable(kbd); /* We shall always use the keyboard in the XLATE mode here. */ vw->vw_prev_kbdmode = vw->vw_kbdmode; vw->vw_kbdmode = K_XLATE; vt_update_kbd_mode(vw, kbd); kbdd_poll(kbd, TRUE); return (1); } static int vtterm_cnungrab_noswitch(struct vt_device *vd, struct vt_window *vw) { keyboard_t *kbd; if (--vw->vw_grabbed > 0) return (vw->vw_grabbed); if ((kbd = vd->vd_keyboard) == NULL) return (0); kbdd_poll(kbd, FALSE); vw->vw_kbdmode = vw->vw_prev_kbdmode; vt_update_kbd_mode(vw, kbd); kbdd_disable(kbd); return (0); } static void vtterm_cngrab(struct terminal *tm) { struct vt_device *vd; struct vt_window *vw; vw = tm->tm_softc; vd = vw->vw_device; /* To be restored after we ungrab. */ if (vd->vd_grabwindow == NULL) vd->vd_grabwindow = vd->vd_curwindow; if (!cold) vt_window_switch(vw); vtterm_cngrab_noswitch(vd, vw); } static void vtterm_cnungrab(struct terminal *tm) { struct vt_device *vd; struct vt_window *vw; vw = tm->tm_softc; vd = vw->vw_device; MPASS(vd->vd_grabwindow != NULL); if (vtterm_cnungrab_noswitch(vd, vw) != 0) return; if (!cold && vd->vd_grabwindow != vw) vt_window_switch(vd->vd_grabwindow); vd->vd_grabwindow = NULL; } static void vtterm_opened(struct terminal *tm, int opened) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; VT_LOCK(vd); vd->vd_flags &= ~VDF_SPLASH; if (opened) vw->vw_flags |= VWF_OPENED; else { vw->vw_flags &= ~VWF_OPENED; /* TODO: finish ACQ/REL */ } VT_UNLOCK(vd); } static int vt_change_font(struct vt_window *vw, struct vt_font *vf) { struct vt_device *vd = vw->vw_device; struct terminal *tm = vw->vw_terminal; term_pos_t size; struct winsize wsz; /* * Changing fonts. * * Changing fonts is a little tricky. We must prevent * simultaneous access to the device, so we must stop * the display timer and the terminal from accessing. * We need to switch fonts and grow our screen buffer. * * XXX: Right now the code uses terminal_mute() to * prevent data from reaching the console driver while * resizing the screen buffer. This isn't elegant... */ VT_LOCK(vd); if (vw->vw_flags & VWF_BUSY) { /* Another process is changing the font. */ VT_UNLOCK(vd); return (EBUSY); } vw->vw_flags |= VWF_BUSY; VT_UNLOCK(vd); vt_termsize(vd, vf, &size); vt_winsize(vd, vf, &wsz); /* Grow the screen buffer and terminal. */ terminal_mute(tm, 1); vtbuf_grow(&vw->vw_buf, &size, vw->vw_buf.vb_history_size); terminal_set_winsize_blank(tm, &wsz, 0, NULL); terminal_set_cursor(tm, &vw->vw_buf.vb_cursor); terminal_mute(tm, 0); /* Actually apply the font to the current window. */ VT_LOCK(vd); if (vw->vw_font != vf && vw->vw_font != NULL && vf != NULL) { /* * In case vt_change_font called to update size we don't need * to update font link. */ vtfont_unref(vw->vw_font); vw->vw_font = vtfont_ref(vf); } /* * Compute the drawable area and move the mouse cursor inside * it, in case the new area is smaller than the previous one. */ vt_compute_drawable_area(vw); vd->vd_mx = min(vd->vd_mx, vw->vw_draw_area.tr_end.tp_col - vw->vw_draw_area.tr_begin.tp_col - 1); vd->vd_my = min(vd->vd_my, vw->vw_draw_area.tr_end.tp_row - vw->vw_draw_area.tr_begin.tp_row - 1); /* Force a full redraw the next timer tick. */ if (vd->vd_curwindow == vw) { vd->vd_flags |= VDF_INVALID; vt_resume_flush_timer(vw, 0); } vw->vw_flags &= ~VWF_BUSY; VT_UNLOCK(vd); return (0); } static int vt_proc_alive(struct vt_window *vw) { struct proc *p; if (vw->vw_smode.mode != VT_PROCESS) return (FALSE); if (vw->vw_proc) { if ((p = pfind(vw->vw_pid)) != NULL) PROC_UNLOCK(p); if (vw->vw_proc == p) return (TRUE); vw->vw_proc = NULL; vw->vw_smode.mode = VT_AUTO; DPRINTF(1, "vt controlling process %d died\n", vw->vw_pid); vw->vw_pid = 0; } return (FALSE); } static int signal_vt_rel(struct vt_window *vw) { if (vw->vw_smode.mode != VT_PROCESS) return (FALSE); if (vw->vw_proc == NULL || vt_proc_alive(vw) == FALSE) { vw->vw_proc = NULL; vw->vw_pid = 0; return (TRUE); } vw->vw_flags |= VWF_SWWAIT_REL; PROC_LOCK(vw->vw_proc); kern_psignal(vw->vw_proc, vw->vw_smode.relsig); PROC_UNLOCK(vw->vw_proc); DPRINTF(1, "sending relsig to %d\n", vw->vw_pid); return (TRUE); } static int signal_vt_acq(struct vt_window *vw) { if (vw->vw_smode.mode != VT_PROCESS) return (FALSE); if (vw == vw->vw_device->vd_windows[VT_CONSWINDOW]) cnavailable(vw->vw_terminal->consdev, FALSE); if (vw->vw_proc == NULL || vt_proc_alive(vw) == FALSE) { vw->vw_proc = NULL; vw->vw_pid = 0; return (TRUE); } vw->vw_flags |= VWF_SWWAIT_ACQ; PROC_LOCK(vw->vw_proc); kern_psignal(vw->vw_proc, vw->vw_smode.acqsig); PROC_UNLOCK(vw->vw_proc); DPRINTF(1, "sending acqsig to %d\n", vw->vw_pid); return (TRUE); } static int finish_vt_rel(struct vt_window *vw, int release, int *s) { if (vw->vw_flags & VWF_SWWAIT_REL) { vw->vw_flags &= ~VWF_SWWAIT_REL; if (release) { callout_drain(&vw->vw_proc_dead_timer); (void)vt_late_window_switch(vw->vw_switch_to); } return (0); } return (EINVAL); } static int finish_vt_acq(struct vt_window *vw) { if (vw->vw_flags & VWF_SWWAIT_ACQ) { vw->vw_flags &= ~VWF_SWWAIT_ACQ; return (0); } return (EINVAL); } #ifndef SC_NO_CUTPASTE static void vt_mouse_terminput_button(struct vt_device *vd, int button) { struct vt_window *vw; struct vt_font *vf; char mouseb[6] = "\x1B[M"; int i, x, y; vw = vd->vd_curwindow; vf = vw->vw_font; /* Translate to char position. */ x = vd->vd_mx / vf->vf_width; y = vd->vd_my / vf->vf_height; /* Avoid overflow. */ x = MIN(x, 255 - '!'); y = MIN(y, 255 - '!'); mouseb[3] = ' ' + button; mouseb[4] = '!' + x; mouseb[5] = '!' + y; for (i = 0; i < sizeof(mouseb); i++) terminal_input_char(vw->vw_terminal, mouseb[i]); } static void vt_mouse_terminput(struct vt_device *vd, int type, int x, int y, int event, int cnt) { switch (type) { case MOUSE_BUTTON_EVENT: if (cnt > 0) { /* Mouse button pressed. */ if (event & MOUSE_BUTTON1DOWN) vt_mouse_terminput_button(vd, 0); if (event & MOUSE_BUTTON2DOWN) vt_mouse_terminput_button(vd, 1); if (event & MOUSE_BUTTON3DOWN) vt_mouse_terminput_button(vd, 2); } else { /* Mouse button released. */ vt_mouse_terminput_button(vd, 3); } break; #ifdef notyet case MOUSE_MOTION_EVENT: if (mouse->u.data.z < 0) { /* Scroll up. */ sc_mouse_input_button(vd, 64); } else if (mouse->u.data.z > 0) { /* Scroll down. */ sc_mouse_input_button(vd, 65); } break; #endif } } static void vt_mouse_paste() { term_char_t *buf; int i, len; len = VD_PASTEBUFLEN(main_vd); buf = VD_PASTEBUF(main_vd); len /= sizeof(term_char_t); for (i = 0; i < len; i++) { if (buf[i] == '\0') continue; terminal_input_char(main_vd->vd_curwindow->vw_terminal, buf[i]); } } void vt_mouse_event(int type, int x, int y, int event, int cnt, int mlevel) { struct vt_device *vd; struct vt_window *vw; struct vt_font *vf; term_pos_t size; int len, mark; vd = main_vd; vw = vd->vd_curwindow; vf = vw->vw_font; mark = 0; if (vw->vw_flags & (VWF_MOUSE_HIDE | VWF_GRAPHICS)) /* * Either the mouse is disabled, or the window is in * "graphics mode". The graphics mode is usually set by * an X server, using the KDSETMODE ioctl. */ return; if (vf == NULL) /* Text mode. */ return; /* * TODO: add flag about pointer position changed, to not redraw chars * under mouse pointer when nothing changed. */ if (vw->vw_mouse_level > 0) vt_mouse_terminput(vd, type, x, y, event, cnt); switch (type) { case MOUSE_ACTION: case MOUSE_MOTION_EVENT: /* Movement */ x += vd->vd_mx; y += vd->vd_my; vt_termsize(vd, vf, &size); /* Apply limits. */ x = MAX(x, 0); y = MAX(y, 0); x = MIN(x, (size.tp_col * vf->vf_width) - 1); y = MIN(y, (size.tp_row * vf->vf_height) - 1); vd->vd_mx = x; vd->vd_my = y; if (vd->vd_mstate & MOUSE_BUTTON1DOWN) vtbuf_set_mark(&vw->vw_buf, VTB_MARK_MOVE, vd->vd_mx / vf->vf_width, vd->vd_my / vf->vf_height); vt_resume_flush_timer(vw, 0); return; /* Done */ case MOUSE_BUTTON_EVENT: /* Buttons */ break; default: return; /* Done */ } switch (event) { case MOUSE_BUTTON1DOWN: switch (cnt % 4) { case 0: /* up */ mark = VTB_MARK_END; break; case 1: /* single click: start cut operation */ mark = VTB_MARK_START; break; case 2: /* double click: cut a word */ mark = VTB_MARK_WORD; break; case 3: /* triple click: cut a line */ mark = VTB_MARK_ROW; break; } break; case VT_MOUSE_PASTEBUTTON: switch (cnt) { case 0: /* up */ break; default: vt_mouse_paste(); break; } return; /* Done */ case VT_MOUSE_EXTENDBUTTON: switch (cnt) { case 0: /* up */ if (!(vd->vd_mstate & MOUSE_BUTTON1DOWN)) mark = VTB_MARK_EXTEND; else mark = 0; break; default: mark = VTB_MARK_EXTEND; break; } break; default: return; /* Done */ } /* Save buttons state. */ if (cnt > 0) vd->vd_mstate |= event; else vd->vd_mstate &= ~event; if (vtbuf_set_mark(&vw->vw_buf, mark, vd->vd_mx / vf->vf_width, vd->vd_my / vf->vf_height) == 1) { /* * We have something marked to copy, so update pointer to * window with selection. */ vt_resume_flush_timer(vw, 0); switch (mark) { case VTB_MARK_END: case VTB_MARK_WORD: case VTB_MARK_ROW: case VTB_MARK_EXTEND: break; default: /* Other types of mark do not require to copy data. */ return; } /* Get current selection size in bytes. */ len = vtbuf_get_marked_len(&vw->vw_buf); if (len <= 0) return; /* Reallocate buffer only if old one is too small. */ if (len > VD_PASTEBUFSZ(vd)) { VD_PASTEBUF(vd) = realloc(VD_PASTEBUF(vd), len, M_VT, M_WAITOK | M_ZERO); /* Update buffer size. */ VD_PASTEBUFSZ(vd) = len; } /* Request copy/paste buffer data, no more than `len' */ vtbuf_extract_marked(&vw->vw_buf, VD_PASTEBUF(vd), VD_PASTEBUFSZ(vd)); VD_PASTEBUFLEN(vd) = len; /* XXX VD_PASTEBUF(vd) have to be freed on shutdown/unload. */ } } void vt_mouse_state(int show) { struct vt_device *vd; struct vt_window *vw; vd = main_vd; vw = vd->vd_curwindow; switch (show) { case VT_MOUSE_HIDE: vw->vw_flags |= VWF_MOUSE_HIDE; break; case VT_MOUSE_SHOW: vw->vw_flags &= ~VWF_MOUSE_HIDE; break; } /* Mark mouse position as dirty. */ vt_mark_mouse_position_as_dirty(vd, false); vt_resume_flush_timer(vw, 0); } #endif static int vtterm_mmap(struct terminal *tm, vm_ooffset_t offset, vm_paddr_t * paddr, int nprot, vm_memattr_t *memattr) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; if (vd->vd_driver->vd_fb_mmap) return (vd->vd_driver->vd_fb_mmap(vd, offset, paddr, nprot, memattr)); return (ENXIO); } static int vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data, struct thread *td) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; keyboard_t *kbd; int error, i, s; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; switch (cmd) { case _IO('v', 4): cmd = VT_RELDISP; break; case _IO('v', 5): cmd = VT_ACTIVATE; break; case _IO('v', 6): cmd = VT_WAITACTIVE; break; case _IO('K', 20): cmd = KDSKBSTATE; break; case _IO('K', 67): cmd = KDSETRAD; break; case _IO('K', 7): cmd = KDSKBMODE; break; case _IO('K', 8): cmd = KDMKTONE; break; case _IO('K', 10): cmd = KDSETMODE; break; case _IO('K', 13): cmd = KDSBORDER; break; case _IO('K', 63): cmd = KIOCSOUND; break; case _IO('K', 66): cmd = KDSETLED; break; case _IO('c', 104): cmd = CONS_SETWINORG; break; case _IO('c', 110): cmd = CONS_SETKBD; break; default: goto skip_thunk; } ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; skip_thunk: #endif switch (cmd) { case KDSETRAD: /* set keyboard repeat & delay rates (old) */ if (*(int *)data & ~0x7f) return (EINVAL); /* FALLTHROUGH */ case GIO_KEYMAP: case PIO_KEYMAP: case GIO_DEADKEYMAP: case PIO_DEADKEYMAP: case GETFKEY: case SETFKEY: case KDGKBINFO: case KDGKBTYPE: case KDGETREPEAT: /* get keyboard repeat & delay rates */ case KDSETREPEAT: /* set keyboard repeat & delay rates (new) */ case KBADDKBD: /* add/remove keyboard to/from mux */ case KBRELKBD: { error = 0; mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = kbdd_ioctl(kbd, cmd, data); mtx_unlock(&Giant); if (error == ENOIOCTL) { if (cmd == KDGKBTYPE) { /* always return something? XXX */ *(int *)data = 0; } else { return (ENODEV); } } return (error); } case KDGKBSTATE: { /* get keyboard state (locks) */ error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = vt_save_kbd_state(vw, kbd); mtx_unlock(&Giant); if (error != 0) return (error); } *(int *)data = vw->vw_kbdstate & LOCK_MASK; return (error); } case KDSKBSTATE: { /* set keyboard state (locks) */ int state; state = *(int *)data; if (state & ~LOCK_MASK) return (EINVAL); vw->vw_kbdstate &= ~LOCK_MASK; vw->vw_kbdstate |= state; error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = vt_update_kbd_state(vw, kbd); mtx_unlock(&Giant); } return (error); } case KDGETLED: { /* get keyboard LED status */ error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = vt_save_kbd_leds(vw, kbd); mtx_unlock(&Giant); if (error != 0) return (error); } *(int *)data = vw->vw_kbdstate & LED_MASK; return (error); } case KDSETLED: { /* set keyboard LED status */ int leds; leds = *(int *)data; if (leds & ~LED_MASK) return (EINVAL); vw->vw_kbdstate &= ~LED_MASK; vw->vw_kbdstate |= leds; error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = vt_update_kbd_leds(vw, kbd); mtx_unlock(&Giant); } return (error); } case KDGETMODE: *(int *)data = (vw->vw_flags & VWF_GRAPHICS) ? KD_GRAPHICS : KD_TEXT; return (0); case KDGKBMODE: { error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = vt_save_kbd_mode(vw, kbd); mtx_unlock(&Giant); if (error != 0) return (error); } *(int *)data = vw->vw_kbdmode; return (error); } case KDSKBMODE: { int mode; mode = *(int *)data; switch (mode) { case K_XLATE: case K_RAW: case K_CODE: vw->vw_kbdmode = mode; error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) error = vt_update_kbd_mode(vw, kbd); mtx_unlock(&Giant); } return (error); default: return (EINVAL); } } case FBIOGTYPE: case FBIO_GETWINORG: /* get frame buffer window origin */ case FBIO_GETDISPSTART: /* get display start address */ case FBIO_GETLINEWIDTH: /* get scan line width in bytes */ case FBIO_BLANK: /* blank display */ if (vd->vd_driver->vd_fb_ioctl) return (vd->vd_driver->vd_fb_ioctl(vd, cmd, data, td)); break; case CONS_BLANKTIME: /* XXX */ return (0); case CONS_HISTORY: if (*(int *)data < 0) return EINVAL; if (*(int *)data != vw->vw_buf.vb_history_size) vtbuf_sethistory_size(&vw->vw_buf, *(int *)data); return (0); case CONS_CLRHIST: vtbuf_clearhistory(&vw->vw_buf); /* * Invalidate the entire visible window; it is not guaranteed * that this operation will be immediately followed by a scroll * event, so it would otherwise be possible for prior artifacts * to remain visible. */ VT_LOCK(vd); if (vw == vd->vd_curwindow) { vd->vd_flags |= VDF_INVALID; vt_resume_flush_timer(vw, 0); } VT_UNLOCK(vd); return (0); case CONS_GET: /* XXX */ *(int *)data = M_CG640x480; return (0); case CONS_BELLTYPE: /* set bell type sound */ if ((*(int *)data) & CONS_QUIET_BELL) vd->vd_flags |= VDF_QUIET_BELL; else vd->vd_flags &= ~VDF_QUIET_BELL; return (0); case CONS_GETINFO: { vid_info_t *vi = (vid_info_t *)data; if (vi->size != sizeof(struct vid_info)) return (EINVAL); if (vw == vd->vd_curwindow) { mtx_lock(&Giant); if ((kbd = vd->vd_keyboard) != NULL) vt_save_kbd_state(vw, kbd); mtx_unlock(&Giant); } vi->m_num = vd->vd_curwindow->vw_number + 1; vi->mk_keylock = vw->vw_kbdstate & LOCK_MASK; /* XXX: other fields! */ return (0); } case CONS_GETVERS: *(int *)data = 0x200; return (0); case CONS_MODEINFO: /* XXX */ return (0); case CONS_MOUSECTL: { mouse_info_t *mouse = (mouse_info_t*)data; /* * All the commands except MOUSE_SHOW nd MOUSE_HIDE * should not be applied to individual TTYs, but only to * consolectl. */ switch (mouse->operation) { case MOUSE_HIDE: if (vd->vd_flags & VDF_MOUSECURSOR) { vd->vd_flags &= ~VDF_MOUSECURSOR; #ifndef SC_NO_CUTPASTE vt_mouse_state(VT_MOUSE_HIDE); #endif } return (0); case MOUSE_SHOW: if (!(vd->vd_flags & VDF_MOUSECURSOR)) { vd->vd_flags |= VDF_MOUSECURSOR; vd->vd_mx = vd->vd_width / 2; vd->vd_my = vd->vd_height / 2; #ifndef SC_NO_CUTPASTE vt_mouse_state(VT_MOUSE_SHOW); #endif } return (0); default: return (EINVAL); } } case PIO_VFONT: { struct vt_font *vf; if (vd->vd_flags & VDF_TEXTMODE) return (ENOTSUP); error = vtfont_load((void *)data, &vf); if (error != 0) return (error); error = vt_change_font(vw, vf); vtfont_unref(vf); return (error); } case PIO_VFONT_DEFAULT: { /* Reset to default font. */ error = vt_change_font(vw, vt_font_assigned); return (error); } case GIO_SCRNMAP: { scrmap_t *sm = (scrmap_t *)data; /* We don't have screen maps, so return a handcrafted one. */ for (i = 0; i < 256; i++) sm->scrmap[i] = i; return (0); } case KDSETMODE: /* * FIXME: This implementation is incomplete compared to * syscons. */ switch (*(int *)data) { case KD_TEXT: case KD_TEXT1: case KD_PIXEL: vw->vw_flags &= ~VWF_GRAPHICS; break; case KD_GRAPHICS: vw->vw_flags |= VWF_GRAPHICS; break; } return (0); case KDENABIO: /* allow io operations */ error = priv_check(td, PRIV_IO); if (error != 0) return (error); error = securelevel_gt(td->td_ucred, 0); if (error != 0) return (error); #if defined(__i386__) td->td_frame->tf_eflags |= PSL_IOPL; #elif defined(__amd64__) td->td_frame->tf_rflags |= PSL_IOPL; #endif return (0); case KDDISABIO: /* disallow io operations (default) */ #if defined(__i386__) td->td_frame->tf_eflags &= ~PSL_IOPL; #elif defined(__amd64__) td->td_frame->tf_rflags &= ~PSL_IOPL; #endif return (0); case KDMKTONE: /* sound the bell */ vtterm_beep(tm, *(u_int *)data); return (0); case KIOCSOUND: /* make tone (*data) hz */ /* TODO */ return (0); case CONS_SETKBD: /* set the new keyboard */ mtx_lock(&Giant); error = 0; if (vd->vd_keyboard == NULL || vd->vd_keyboard->kb_index != *(int *)data) { kbd = kbd_get_keyboard(*(int *)data); if (kbd == NULL) { mtx_unlock(&Giant); return (EINVAL); } i = kbd_allocate(kbd->kb_name, kbd->kb_unit, (void *)vd, vt_kbdevent, vd); if (i >= 0) { if ((kbd = vd->vd_keyboard) != NULL) { vt_save_kbd_state(vd->vd_curwindow, kbd); kbd_release(kbd, (void *)vd); } kbd = vd->vd_keyboard = kbd_get_keyboard(i); vt_update_kbd_mode(vd->vd_curwindow, kbd); vt_update_kbd_state(vd->vd_curwindow, kbd); } else { error = EPERM; /* XXX */ } } mtx_unlock(&Giant); return (error); case CONS_RELKBD: /* release the current keyboard */ mtx_lock(&Giant); error = 0; if ((kbd = vd->vd_keyboard) != NULL) { vt_save_kbd_state(vd->vd_curwindow, kbd); error = kbd_release(kbd, (void *)vd); if (error == 0) { vd->vd_keyboard = NULL; } } mtx_unlock(&Giant); return (error); case VT_ACTIVATE: { int win; win = *(int *)data - 1; DPRINTF(5, "%s%d: VT_ACTIVATE ttyv%d ", SC_DRIVER_NAME, VT_UNIT(vw), win); if ((win >= VT_MAXWINDOWS) || (win < 0)) return (EINVAL); return (vt_proc_window_switch(vd->vd_windows[win])); } case VT_GETACTIVE: *(int *)data = vd->vd_curwindow->vw_number + 1; return (0); case VT_GETINDEX: *(int *)data = vw->vw_number + 1; return (0); case VT_LOCKSWITCH: /* TODO: Check current state, switching can be in progress. */ if ((*(int *)data) == 0x01) vw->vw_flags |= VWF_VTYLOCK; else if ((*(int *)data) == 0x02) vw->vw_flags &= ~VWF_VTYLOCK; else return (EINVAL); return (0); case VT_OPENQRY: VT_LOCK(vd); for (i = 0; i < VT_MAXWINDOWS; i++) { vw = vd->vd_windows[i]; if (vw == NULL) continue; if (!(vw->vw_flags & VWF_OPENED)) { *(int *)data = vw->vw_number + 1; VT_UNLOCK(vd); return (0); } } VT_UNLOCK(vd); return (EINVAL); case VT_WAITACTIVE: { unsigned int idx; error = 0; idx = *(unsigned int *)data; if (idx > VT_MAXWINDOWS) return (EINVAL); if (idx > 0) vw = vd->vd_windows[idx - 1]; VT_LOCK(vd); while (vd->vd_curwindow != vw && error == 0) error = cv_wait_sig(&vd->vd_winswitch, &vd->vd_lock); VT_UNLOCK(vd); return (error); } case VT_SETMODE: { /* set screen switcher mode */ struct vt_mode *mode; struct proc *p1; mode = (struct vt_mode *)data; DPRINTF(5, "%s%d: VT_SETMODE ", SC_DRIVER_NAME, VT_UNIT(vw)); if (vw->vw_smode.mode == VT_PROCESS) { p1 = pfind(vw->vw_pid); if (vw->vw_proc == p1 && vw->vw_proc != td->td_proc) { if (p1) PROC_UNLOCK(p1); DPRINTF(5, "error EPERM\n"); return (EPERM); } if (p1) PROC_UNLOCK(p1); } if (mode->mode == VT_AUTO) { vw->vw_smode.mode = VT_AUTO; vw->vw_proc = NULL; vw->vw_pid = 0; DPRINTF(5, "VT_AUTO, "); if (vw == vw->vw_device->vd_windows[VT_CONSWINDOW]) cnavailable(vw->vw_terminal->consdev, TRUE); /* were we in the middle of the vty switching process? */ if (finish_vt_rel(vw, TRUE, &s) == 0) DPRINTF(5, "reset WAIT_REL, "); if (finish_vt_acq(vw) == 0) DPRINTF(5, "reset WAIT_ACQ, "); return (0); } else if (mode->mode == VT_PROCESS) { if (!ISSIGVALID(mode->relsig) || !ISSIGVALID(mode->acqsig) || !ISSIGVALID(mode->frsig)) { DPRINTF(5, "error EINVAL\n"); return (EINVAL); } DPRINTF(5, "VT_PROCESS %d, ", td->td_proc->p_pid); bcopy(data, &vw->vw_smode, sizeof(struct vt_mode)); vw->vw_proc = td->td_proc; vw->vw_pid = vw->vw_proc->p_pid; if (vw == vw->vw_device->vd_windows[VT_CONSWINDOW]) cnavailable(vw->vw_terminal->consdev, FALSE); } else { DPRINTF(5, "VT_SETMODE failed, unknown mode %d\n", mode->mode); return (EINVAL); } DPRINTF(5, "\n"); return (0); } case VT_GETMODE: /* get screen switcher mode */ bcopy(&vw->vw_smode, data, sizeof(struct vt_mode)); return (0); case VT_RELDISP: /* screen switcher ioctl */ /* * This must be the current vty which is in the VT_PROCESS * switching mode... */ if ((vw != vd->vd_curwindow) || (vw->vw_smode.mode != VT_PROCESS)) { return (EINVAL); } /* ...and this process is controlling it. */ if (vw->vw_proc != td->td_proc) { return (EPERM); } error = EINVAL; switch(*(int *)data) { case VT_FALSE: /* user refuses to release screen, abort */ if ((error = finish_vt_rel(vw, FALSE, &s)) == 0) DPRINTF(5, "%s%d: VT_RELDISP: VT_FALSE\n", SC_DRIVER_NAME, VT_UNIT(vw)); break; case VT_TRUE: /* user has released screen, go on */ /* finish_vt_rel(..., TRUE, ...) should not be locked */ if (vw->vw_flags & VWF_SWWAIT_REL) { if ((error = finish_vt_rel(vw, TRUE, &s)) == 0) DPRINTF(5, "%s%d: VT_RELDISP: VT_TRUE\n", SC_DRIVER_NAME, VT_UNIT(vw)); } else { error = EINVAL; } return (error); case VT_ACKACQ: /* acquire acknowledged, switch completed */ if ((error = finish_vt_acq(vw)) == 0) DPRINTF(5, "%s%d: VT_RELDISP: VT_ACKACQ\n", SC_DRIVER_NAME, VT_UNIT(vw)); break; default: break; } return (error); } return (ENOIOCTL); } static struct vt_window * vt_allocate_window(struct vt_device *vd, unsigned int window) { struct vt_window *vw; struct terminal *tm; term_pos_t size; struct winsize wsz; vw = malloc(sizeof *vw, M_VT, M_WAITOK|M_ZERO); vw->vw_device = vd; vw->vw_number = window; vw->vw_kbdmode = K_XLATE; if ((vd->vd_flags & VDF_TEXTMODE) == 0) { vw->vw_font = vtfont_ref(vt_font_assigned); vt_compute_drawable_area(vw); } vt_termsize(vd, vw->vw_font, &size); vt_winsize(vd, vw->vw_font, &wsz); tm = vw->vw_terminal = terminal_alloc(&vt_termclass, vw); vw->vw_buf.vb_terminal = tm; /* must be set before vtbuf_init() */ vtbuf_init(&vw->vw_buf, &size); terminal_set_winsize(tm, &wsz); vd->vd_windows[window] = vw; callout_init(&vw->vw_proc_dead_timer, 1); return (vw); } void vt_upgrade(struct vt_device *vd) { struct vt_window *vw; unsigned int i; int register_handlers; if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_driver == NULL) return; for (i = 0; i < VT_MAXWINDOWS; i++) { vw = vd->vd_windows[i]; if (vw == NULL) { /* New window. */ vw = vt_allocate_window(vd, i); } if (!(vw->vw_flags & VWF_READY)) { callout_init(&vw->vw_proc_dead_timer, 1); terminal_maketty(vw->vw_terminal, "v%r", VT_UNIT(vw)); vw->vw_flags |= VWF_READY; if (vw->vw_flags & VWF_CONSOLE) { /* For existing console window. */ EVENTHANDLER_REGISTER(shutdown_pre_sync, vt_window_switch, vw, SHUTDOWN_PRI_DEFAULT); } } } VT_LOCK(vd); if (vd->vd_curwindow == NULL) vd->vd_curwindow = vd->vd_windows[VT_CONSWINDOW]; register_handlers = 0; if (!(vd->vd_flags & VDF_ASYNC)) { /* Attach keyboard. */ vt_allocate_keyboard(vd); /* Init 25 Hz timer. */ callout_init_mtx(&vd->vd_timer, &vd->vd_lock, 0); /* * Start timer when everything ready. * Note that the operations here are purposefully ordered. * We need to ensure vd_timer_armed is non-zero before we set * the VDF_ASYNC flag. That prevents this function from * racing with vt_resume_flush_timer() to update the * callout structure. */ atomic_add_acq_int(&vd->vd_timer_armed, 1); vd->vd_flags |= VDF_ASYNC; callout_reset(&vd->vd_timer, hz / VT_TIMERFREQ, vt_timer, vd); register_handlers = 1; } VT_UNLOCK(vd); /* Refill settings with new sizes. */ vt_resize(vd); if (register_handlers) { /* Register suspend/resume handlers. */ EVENTHANDLER_REGISTER(power_suspend_early, vt_suspend_handler, vd, EVENTHANDLER_PRI_ANY); EVENTHANDLER_REGISTER(power_resume, vt_resume_handler, vd, EVENTHANDLER_PRI_ANY); } } static void vt_resize(struct vt_device *vd) { struct vt_window *vw; int i; for (i = 0; i < VT_MAXWINDOWS; i++) { vw = vd->vd_windows[i]; VT_LOCK(vd); /* Assign default font to window, if not textmode. */ if (!(vd->vd_flags & VDF_TEXTMODE) && vw->vw_font == NULL) vw->vw_font = vtfont_ref(vt_font_assigned); VT_UNLOCK(vd); /* Resize terminal windows */ while (vt_change_font(vw, vw->vw_font) == EBUSY) { DPRINTF(100, "%s: vt_change_font() is busy, " "window %d\n", __func__, i); } } } static void vt_replace_backend(const struct vt_driver *drv, void *softc) { struct vt_device *vd; vd = main_vd; if (vd->vd_flags & VDF_ASYNC) { /* Stop vt_flush periodic task. */ VT_LOCK(vd); vt_suspend_flush_timer(vd); VT_UNLOCK(vd); /* * Mute current terminal until we done. vt_change_font (called * from vt_resize) will unmute it. */ terminal_mute(vd->vd_curwindow->vw_terminal, 1); } /* * Reset VDF_TEXTMODE flag, driver who require that flag (vt_vga) will * set it. */ VT_LOCK(vd); vd->vd_flags &= ~VDF_TEXTMODE; if (drv != NULL) { /* * We want to upgrade from the current driver to the * given driver. */ vd->vd_prev_driver = vd->vd_driver; vd->vd_prev_softc = vd->vd_softc; vd->vd_driver = drv; vd->vd_softc = softc; vd->vd_driver->vd_init(vd); } else if (vd->vd_prev_driver != NULL && vd->vd_prev_softc != NULL) { /* * No driver given: we want to downgrade to the previous * driver. */ const struct vt_driver *old_drv; void *old_softc; old_drv = vd->vd_driver; old_softc = vd->vd_softc; vd->vd_driver = vd->vd_prev_driver; vd->vd_softc = vd->vd_prev_softc; vd->vd_prev_driver = NULL; vd->vd_prev_softc = NULL; vd->vd_flags |= VDF_DOWNGRADE; vd->vd_driver->vd_init(vd); if (old_drv->vd_fini) old_drv->vd_fini(vd, old_softc); vd->vd_flags &= ~VDF_DOWNGRADE; } VT_UNLOCK(vd); /* Update windows sizes and initialize last items. */ vt_upgrade(vd); #ifdef DEV_SPLASH if (vd->vd_flags & VDF_SPLASH) vtterm_splash(vd); #endif if (vd->vd_flags & VDF_ASYNC) { /* Allow to put chars now. */ terminal_mute(vd->vd_curwindow->vw_terminal, 0); /* Rerun timer for screen updates. */ vt_resume_flush_timer(vd->vd_curwindow, 0); } /* * Register as console. If it already registered, cnadd() will ignore * it. */ termcn_cnregister(vd->vd_windows[VT_CONSWINDOW]->vw_terminal); } static void vt_suspend_handler(void *priv) { struct vt_device *vd; vd = priv; vd->vd_flags |= VDF_SUSPENDED; if (vd->vd_driver != NULL && vd->vd_driver->vd_suspend != NULL) vd->vd_driver->vd_suspend(vd); } static void vt_resume_handler(void *priv) { struct vt_device *vd; vd = priv; if (vd->vd_driver != NULL && vd->vd_driver->vd_resume != NULL) vd->vd_driver->vd_resume(vd); vd->vd_flags &= ~VDF_SUSPENDED; } void vt_allocate(const struct vt_driver *drv, void *softc) { if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_driver == NULL) { main_vd->vd_driver = drv; printf("VT: initialize with new VT driver \"%s\".\n", drv->vd_name); } else { /* * Check if have rights to replace current driver. For example: * it is bad idea to replace KMS driver with generic VGA one. */ if (drv->vd_priority <= main_vd->vd_driver->vd_priority) { printf("VT: Driver priority %d too low. Current %d\n ", drv->vd_priority, main_vd->vd_driver->vd_priority); return; } printf("VT: Replacing driver \"%s\" with new \"%s\".\n", main_vd->vd_driver->vd_name, drv->vd_name); } vt_replace_backend(drv, softc); } void vt_deallocate(const struct vt_driver *drv, void *softc) { if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_prev_driver == NULL || main_vd->vd_driver != drv || main_vd->vd_softc != softc) return; printf("VT: Switching back from \"%s\" to \"%s\".\n", main_vd->vd_driver->vd_name, main_vd->vd_prev_driver->vd_name); vt_replace_backend(NULL, NULL); } void vt_suspend(struct vt_device *vd) { int error; if (vt_suspendswitch == 0) return; /* Save current window. */ vd->vd_savedwindow = vd->vd_curwindow; /* Ask holding process to free window and switch to console window */ vt_proc_window_switch(vd->vd_windows[VT_CONSWINDOW]); /* Wait for the window switch to complete. */ error = 0; VT_LOCK(vd); while (vd->vd_curwindow != vd->vd_windows[VT_CONSWINDOW] && error == 0) error = cv_wait_sig(&vd->vd_winswitch, &vd->vd_lock); VT_UNLOCK(vd); } void vt_resume(struct vt_device *vd) { if (vt_suspendswitch == 0) return; /* Switch back to saved window, if any */ vt_proc_window_switch(vd->vd_savedwindow); vd->vd_savedwindow = NULL; } diff --git a/sys/i386/i386/trap.c b/sys/i386/i386/trap.c index 07abac23c9da..d770cf808f5f 100644 --- a/sys/i386/i386/trap.c +++ b/sys/i386/i386/trap.c @@ -1,1148 +1,1148 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (C) 1994, David Greenman * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * the University of Utah, and William Jolitz. * * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 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. * * from: @(#)trap.c 7.4 (Berkeley) 5/13/91 */ #include __FBSDID("$FreeBSD$"); /* * 386 Trap and System call handling */ #include "opt_clock.h" #include "opt_compat.h" #include "opt_cpu.h" #include "opt_hwpmc_hooks.h" #include "opt_isa.h" #include "opt_kdb.h" #include "opt_trap.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HWPMC_HOOKS #include PMC_SOFT_DEFINE( , , page_fault, all); PMC_SOFT_DEFINE( , , page_fault, read); PMC_SOFT_DEFINE( , , page_fault, write); #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SMP #include #endif #include #include #include #include #ifdef POWERFAIL_NMI #include #include #endif #ifdef KDTRACE_HOOKS #include #endif void trap(struct trapframe *frame); void syscall(struct trapframe *frame); static int trap_pfault(struct trapframe *, bool, vm_offset_t, int *, int *); static void trap_fatal(struct trapframe *, vm_offset_t); #ifdef KDTRACE_HOOKS static bool trap_user_dtrace(struct trapframe *, int (**hook)(struct trapframe *)); #endif void dblfault_handler(void); extern inthand_t IDTVEC(bpt), IDTVEC(dbg), IDTVEC(int0x80_syscall); extern uint64_t pg_nx; struct trap_data { bool ei; const char *msg; }; static const struct trap_data trap_data[] = { [T_PRIVINFLT] = { .ei = true, .msg = "privileged instruction fault" }, [T_BPTFLT] = { .ei = false, .msg = "breakpoint instruction fault" }, [T_ARITHTRAP] = { .ei = true, .msg = "arithmetic trap" }, [T_PROTFLT] = { .ei = true, .msg = "general protection fault" }, [T_TRCTRAP] = { .ei = false, .msg = "debug exception" }, [T_PAGEFLT] = { .ei = true, .msg = "page fault" }, [T_ALIGNFLT] = { .ei = true, .msg = "alignment fault" }, [T_DIVIDE] = { .ei = true, .msg = "integer divide fault" }, [T_NMI] = { .ei = false, .msg = "non-maskable interrupt trap" }, [T_OFLOW] = { .ei = true, .msg = "overflow trap" }, [T_BOUND] = { .ei = true, .msg = "FPU bounds check fault" }, [T_DNA] = { .ei = true, .msg = "FPU device not available" }, [T_DOUBLEFLT] = { .ei = false, .msg = "double fault" }, [T_FPOPFLT] = { .ei = true, .msg = "FPU operand fetch fault" }, [T_TSSFLT] = { .ei = true, .msg = "invalid TSS fault" }, [T_SEGNPFLT] = { .ei = true, .msg = "segment not present fault" }, [T_STKFLT] = { .ei = true, .msg = "stack fault" }, [T_MCHK] = { .ei = true, .msg = "machine check trap" }, [T_XMMFLT] = { .ei = true, .msg = "SIMD floating-point exception" }, [T_DTRACE_RET] ={ .ei = true, .msg = "DTrace pid return trap" }, }; static bool trap_enable_intr(int trapno) { MPASS(trapno > 0); if (trapno < nitems(trap_data) && trap_data[trapno].msg != NULL) return (trap_data[trapno].ei); return (false); } static const char * trap_msg(int trapno) { const char *res; static const char unkn[] = "UNKNOWN"; res = NULL; if (trapno < nitems(trap_data)) res = trap_data[trapno].msg; if (res == NULL) res = unkn; return (res); } #if defined(I586_CPU) && !defined(NO_F00F_HACK) int has_f00f_bug = 0; /* Initialized so that it can be patched. */ #endif static int uprintf_signal; SYSCTL_INT(_machdep, OID_AUTO, uprintf_signal, CTLFLAG_RW, &uprintf_signal, 0, "Print debugging information on trap signal to ctty"); /* * Exception, fault, and trap interface to the FreeBSD kernel. * This common code is called from assembly language IDT gate entry * routines that prepare a suitable stack frame, and restore this * frame after the exception has been processed. */ void trap(struct trapframe *frame) { ksiginfo_t ksi; struct thread *td; struct proc *p; int pf, signo, ucode; u_int type; register_t addr, dr6; vm_offset_t eva; #ifdef POWERFAIL_NMI static int lastalert = 0; #endif td = curthread; p = td->td_proc; dr6 = 0; VM_CNT_INC(v_trap); type = frame->tf_trapno; KASSERT((read_eflags() & PSL_I) == 0, ("trap: interrupts enabled, type %d frame %p", type, frame)); #ifdef SMP /* Handler for NMI IPIs used for stopping CPUs. */ if (type == T_NMI && ipi_nmi_handler() == 0) return; #endif /* SMP */ #ifdef KDB if (kdb_active) { kdb_reenter(); return; } #endif if (type == T_RESERVED) { trap_fatal(frame, 0); return; } if (type == T_NMI) { #ifdef HWPMC_HOOKS /* * CPU PMCs interrupt using an NMI so we check for that first. * If the HWPMC module is active, 'pmc_hook' will point to * the function to be called. A non-zero return value from the * hook means that the NMI was consumed by it and that we can * return immediately. */ if (pmc_intr != NULL && (*pmc_intr)(frame) != 0) return; #endif } if (type == T_MCHK) { mca_intr(); return; } #ifdef KDTRACE_HOOKS /* * A trap can occur while DTrace executes a probe. Before * executing the probe, DTrace blocks re-scheduling and sets * a flag in its per-cpu flags to indicate that it doesn't * want to fault. On returning from the probe, the no-fault * flag is cleared and finally re-scheduling is enabled. */ if ((type == T_PROTFLT || type == T_PAGEFLT) && dtrace_trap_func != NULL && (*dtrace_trap_func)(frame, type)) return; #endif /* * We must not allow context switches until %cr2 is read. * Also, for some Cyrix CPUs, %cr2 is clobbered by interrupts. * All faults use interrupt gates, so %cr2 can be safely read * now, before optional enable of the interrupts below. */ if (type == T_PAGEFLT) eva = rcr2(); /* * Buggy application or kernel code has disabled interrupts * and then trapped. Enabling interrupts now is wrong, but it * is better than running with interrupts disabled until they * are accidentally enabled later. */ if ((frame->tf_eflags & PSL_I) == 0 && TRAPF_USERMODE(frame) && (curpcb->pcb_flags & PCB_VM86CALL) == 0) uprintf("pid %ld (%s): trap %d with interrupts disabled\n", (long)curproc->p_pid, curthread->td_name, type); /* * Conditionally reenable interrupts. If we hold a spin lock, * then we must not reenable interrupts. This might be a * spurious page fault. */ if (trap_enable_intr(type) && td->td_md.md_spinlock_count == 0 && frame->tf_eip != (int)cpu_switch_load_gs) enable_intr(); if (TRAPF_USERMODE(frame) && (curpcb->pcb_flags & PCB_VM86CALL) == 0) { /* user trap */ td->td_pticks = 0; td->td_frame = frame; addr = frame->tf_eip; if (td->td_cowgen != p->p_cowgen) thread_cow_update(td); switch (type) { case T_PRIVINFLT: /* privileged instruction fault */ signo = SIGILL; ucode = ILL_PRVOPC; break; case T_BPTFLT: /* bpt instruction fault */ #ifdef KDTRACE_HOOKS if (trap_user_dtrace(frame, &dtrace_pid_probe_ptr)) return; #else enable_intr(); #endif signo = SIGTRAP; ucode = TRAP_BRKPT; break; case T_TRCTRAP: /* debug exception */ enable_intr(); user_trctrap_out: signo = SIGTRAP; ucode = TRAP_TRACE; dr6 = rdr6(); if ((dr6 & DBREG_DR6_BS) != 0) { PROC_LOCK(td->td_proc); if ((td->td_dbgflags & TDB_STEP) != 0) { td->td_frame->tf_eflags &= ~PSL_T; td->td_dbgflags &= ~TDB_STEP; } PROC_UNLOCK(td->td_proc); } break; case T_ARITHTRAP: /* arithmetic trap */ ucode = npxtrap_x87(); if (ucode == -1) return; signo = SIGFPE; break; /* * The following two traps can happen in vm86 mode, * and, if so, we want to handle them specially. */ case T_PROTFLT: /* general protection fault */ case T_STKFLT: /* stack fault */ if (frame->tf_eflags & PSL_VM) { signo = vm86_emulate((struct vm86frame *)frame); ucode = 0; /* XXXKIB: better code ? */ if (signo == SIGTRAP) { load_dr6(rdr6() | 0x4000); goto user_trctrap_out; } if (signo == 0) goto user; break; } signo = SIGBUS; ucode = (type == T_PROTFLT) ? BUS_OBJERR : BUS_ADRERR; break; case T_SEGNPFLT: /* segment not present fault */ signo = SIGBUS; ucode = BUS_ADRERR; break; case T_TSSFLT: /* invalid TSS fault */ signo = SIGBUS; ucode = BUS_OBJERR; break; case T_ALIGNFLT: signo = SIGBUS; ucode = BUS_ADRALN; break; case T_DOUBLEFLT: /* double fault */ default: signo = SIGBUS; ucode = BUS_OBJERR; break; case T_PAGEFLT: /* page fault */ addr = eva; pf = trap_pfault(frame, true, eva, &signo, &ucode); #if defined(I586_CPU) && !defined(NO_F00F_HACK) if (pf == -2) { /* * The f00f hack workaround has triggered, so * treat the fault as an illegal instruction * (T_PRIVINFLT) instead of a page fault. */ type = frame->tf_trapno = T_PRIVINFLT; break; } #endif if (pf == -1) return; if (pf == 0) goto user; break; case T_DIVIDE: /* integer divide fault */ ucode = FPE_INTDIV; signo = SIGFPE; break; case T_NMI: #ifdef POWERFAIL_NMI #ifndef TIMER_FREQ # define TIMER_FREQ 1193182 #endif if (time_second - lastalert > 10) { log(LOG_WARNING, "NMI: power fail\n"); - sysbeep(880, hz); + sysbeep(880, SBT_1S); lastalert = time_second; } return; #else /* !POWERFAIL_NMI */ nmi_handle_intr(type, frame); return; #endif /* POWERFAIL_NMI */ case T_OFLOW: /* integer overflow fault */ ucode = FPE_INTOVF; signo = SIGFPE; break; case T_BOUND: /* bounds check fault */ ucode = FPE_FLTSUB; signo = SIGFPE; break; case T_DNA: KASSERT(PCB_USER_FPU(td->td_pcb), ("kernel FPU ctx has leaked")); /* transparent fault (due to context switch "late") */ if (npxdna()) return; uprintf("pid %d killed due to lack of floating point\n", p->p_pid); signo = SIGKILL; ucode = 0; break; case T_FPOPFLT: /* FPU operand fetch fault */ ucode = ILL_COPROC; signo = SIGILL; break; case T_XMMFLT: /* SIMD floating-point exception */ ucode = npxtrap_sse(); if (ucode == -1) return; signo = SIGFPE; break; #ifdef KDTRACE_HOOKS case T_DTRACE_RET: (void)trap_user_dtrace(frame, &dtrace_return_probe_ptr); return; #endif } } else { /* kernel trap */ KASSERT(cold || td->td_ucred != NULL, ("kernel trap doesn't have ucred")); switch (type) { case T_PAGEFLT: /* page fault */ (void)trap_pfault(frame, false, eva, NULL, NULL); return; case T_DNA: if (PCB_USER_FPU(td->td_pcb)) panic("Unregistered use of FPU in kernel"); if (npxdna()) return; break; case T_ARITHTRAP: /* arithmetic trap */ case T_XMMFLT: /* SIMD floating-point exception */ case T_FPOPFLT: /* FPU operand fetch fault */ /* * XXXKIB for now disable any FPU traps in kernel * handler registration seems to be overkill */ trap_fatal(frame, 0); return; /* * The following two traps can happen in * vm86 mode, and, if so, we want to handle * them specially. */ case T_PROTFLT: /* general protection fault */ case T_STKFLT: /* stack fault */ if (frame->tf_eflags & PSL_VM) { signo = vm86_emulate((struct vm86frame *)frame); if (signo == SIGTRAP) { type = T_TRCTRAP; load_dr6(rdr6() | 0x4000); goto kernel_trctrap; } if (signo != 0) /* * returns to original process */ vm86_trap((struct vm86frame *)frame); return; } /* FALL THROUGH */ case T_SEGNPFLT: /* segment not present fault */ if (curpcb->pcb_flags & PCB_VM86CALL) break; /* * Invalid %fs's and %gs's can be created using * procfs or PT_SETREGS or by invalidating the * underlying LDT entry. This causes a fault * in kernel mode when the kernel attempts to * switch contexts. Lose the bad context * (XXX) so that we can continue, and generate * a signal. */ if (frame->tf_eip == (int)cpu_switch_load_gs) { curpcb->pcb_gs = 0; #if 0 PROC_LOCK(p); kern_psignal(p, SIGBUS); PROC_UNLOCK(p); #endif return; } if (td->td_intr_nesting_level != 0) break; /* * Invalid segment selectors and out of bounds * %eip's and %esp's can be set up in user mode. * This causes a fault in kernel mode when the * kernel tries to return to user mode. We want * to get this fault so that we can fix the * problem here and not have to check all the * selectors and pointers when the user changes * them. * * N.B. Comparing to long mode, 32-bit mode * does not push %esp on the trap frame, * because iretl faulted while in ring 0. As * the consequence, there is no need to fixup * the stack pointer for doreti_iret_fault, * the fixup and the complimentary trap() call * are executed on the main thread stack, not * on the trampoline stack. */ if (frame->tf_eip == (int)doreti_iret + setidt_disp) { frame->tf_eip = (int)doreti_iret_fault + setidt_disp; return; } if (type == T_STKFLT) break; if (frame->tf_eip == (int)doreti_popl_ds + setidt_disp) { frame->tf_eip = (int)doreti_popl_ds_fault + setidt_disp; return; } if (frame->tf_eip == (int)doreti_popl_es + setidt_disp) { frame->tf_eip = (int)doreti_popl_es_fault + setidt_disp; return; } if (frame->tf_eip == (int)doreti_popl_fs + setidt_disp) { frame->tf_eip = (int)doreti_popl_fs_fault + setidt_disp; return; } if (curpcb->pcb_onfault != NULL) { frame->tf_eip = (int)curpcb->pcb_onfault; return; } break; case T_TSSFLT: /* * PSL_NT can be set in user mode and isn't cleared * automatically when the kernel is entered. This * causes a TSS fault when the kernel attempts to * `iret' because the TSS link is uninitialized. We * want to get this fault so that we can fix the * problem here and not every time the kernel is * entered. */ if (frame->tf_eflags & PSL_NT) { frame->tf_eflags &= ~PSL_NT; return; } break; case T_TRCTRAP: /* debug exception */ kernel_trctrap: /* Clear any pending debug events. */ dr6 = rdr6(); load_dr6(0); /* * Ignore debug register exceptions due to * accesses in the user's address space, which * can happen under several conditions such as * if a user sets a watchpoint on a buffer and * then passes that buffer to a system call. * We still want to get TRCTRAPS for addresses * in kernel space because that is useful when * debugging the kernel. */ if (user_dbreg_trap(dr6) && !(curpcb->pcb_flags & PCB_VM86CALL)) return; /* * Malicious user code can configure a debug * register watchpoint to trap on data access * to the top of stack and then execute 'pop * %ss; int 3'. Due to exception deferral for * 'pop %ss', the CPU will not interrupt 'int * 3' to raise the DB# exception for the debug * register but will postpone the DB# until * execution of the first instruction of the * BP# handler (in kernel mode). Normally the * previous check would ignore DB# exceptions * for watchpoints on user addresses raised in * kernel mode. However, some CPU errata * include cases where DB# exceptions do not * properly set bits in %dr6, e.g. Haswell * HSD23 and Skylake-X SKZ24. * * A deferred DB# can also be raised on the * first instructions of system call entry * points or single-step traps via similar use * of 'pop %ss' or 'mov xxx, %ss'. */ if (frame->tf_eip == (uintptr_t)IDTVEC(int0x80_syscall) + setidt_disp || frame->tf_eip == (uintptr_t)IDTVEC(bpt) + setidt_disp || frame->tf_eip == (uintptr_t)IDTVEC(dbg) + setidt_disp) return; /* * FALLTHROUGH (TRCTRAP kernel mode, kernel address) */ case T_BPTFLT: /* * If KDB is enabled, let it handle the debugger trap. * Otherwise, debugger traps "can't happen". */ #ifdef KDB if (kdb_trap(type, dr6, frame)) return; #endif break; case T_NMI: #ifdef POWERFAIL_NMI if (time_second - lastalert > 10) { log(LOG_WARNING, "NMI: power fail\n"); - sysbeep(880, hz); + sysbeep(880, SBT_1S); lastalert = time_second; } return; #else /* !POWERFAIL_NMI */ nmi_handle_intr(type, frame); return; #endif /* POWERFAIL_NMI */ } trap_fatal(frame, eva); return; } /* Translate fault for emulators (e.g. Linux) */ if (*p->p_sysent->sv_transtrap != NULL) signo = (*p->p_sysent->sv_transtrap)(signo, type); ksiginfo_init_trap(&ksi); ksi.ksi_signo = signo; ksi.ksi_code = ucode; ksi.ksi_addr = (void *)addr; ksi.ksi_trapno = type; if (uprintf_signal) { uprintf("pid %d comm %s: signal %d err %x code %d type %d " "addr 0x%x ss 0x%04x esp 0x%08x cs 0x%04x eip 0x%08x " "<%02x %02x %02x %02x %02x %02x %02x %02x>\n", p->p_pid, p->p_comm, signo, frame->tf_err, ucode, type, addr, frame->tf_ss, frame->tf_esp, frame->tf_cs, frame->tf_eip, fubyte((void *)(frame->tf_eip + 0)), fubyte((void *)(frame->tf_eip + 1)), fubyte((void *)(frame->tf_eip + 2)), fubyte((void *)(frame->tf_eip + 3)), fubyte((void *)(frame->tf_eip + 4)), fubyte((void *)(frame->tf_eip + 5)), fubyte((void *)(frame->tf_eip + 6)), fubyte((void *)(frame->tf_eip + 7))); } KASSERT((read_eflags() & PSL_I) != 0, ("interrupts disabled")); trapsignal(td, &ksi); user: userret(td, frame); KASSERT(PCB_USER_FPU(td->td_pcb), ("Return from trap with kernel FPU ctx leaked")); } /* * Handle all details of a page fault. * Returns: * -2 if the fault was caused by triggered workaround for Intel Pentium * 0xf00f bug. * -1 if this fault was fatal, typically from kernel mode * (cannot happen, but we need to return something). * 0 if this fault was handled by updating either the user or kernel * page table, execution can continue. * 1 if this fault was from usermode and it was not handled, a synchronous * signal should be delivered to the thread. *signo returns the signal * number, *ucode gives si_code. */ static int trap_pfault(struct trapframe *frame, bool usermode, vm_offset_t eva, int *signo, int *ucode) { struct thread *td; struct proc *p; vm_map_t map; int rv; vm_prot_t ftype; MPASS(!usermode || (signo != NULL && ucode != NULL)); td = curthread; p = td->td_proc; if (__predict_false((td->td_pflags & TDP_NOFAULTING) != 0)) { /* * Due to both processor errata and lazy TLB invalidation when * access restrictions are removed from virtual pages, memory * accesses that are allowed by the physical mapping layer may * nonetheless cause one spurious page fault per virtual page. * When the thread is executing a "no faulting" section that * is bracketed by vm_fault_{disable,enable}_pagefaults(), * every page fault is treated as a spurious page fault, * unless it accesses the same virtual address as the most * recent page fault within the same "no faulting" section. */ if (td->td_md.md_spurflt_addr != eva || (td->td_pflags & TDP_RESETSPUR) != 0) { /* * Do nothing to the TLB. A stale TLB entry is * flushed automatically by a page fault. */ td->td_md.md_spurflt_addr = eva; td->td_pflags &= ~TDP_RESETSPUR; return (0); } } else { /* * If we get a page fault while in a critical section, then * it is most likely a fatal kernel page fault. The kernel * is already going to panic trying to get a sleep lock to * do the VM lookup, so just consider it a fatal trap so the * kernel can print out a useful trap message and even get * to the debugger. * * If we get a page fault while holding a non-sleepable * lock, then it is most likely a fatal kernel page fault. * If WITNESS is enabled, then it's going to whine about * bogus LORs with various VM locks, so just skip to the * fatal trap handling directly. */ if (td->td_critnest != 0 || WITNESS_CHECK(WARN_SLEEPOK | WARN_GIANTOK, NULL, "Kernel page fault") != 0) { trap_fatal(frame, eva); return (-1); } } if (eva >= PMAP_TRM_MIN_ADDRESS) { /* * Don't allow user-mode faults in kernel address space. * An exception: if the faulting address is the invalid * instruction entry in the IDT, then the Intel Pentium * F00F bug workaround was triggered, and we need to * treat it is as an illegal instruction, and not a page * fault. */ #if defined(I586_CPU) && !defined(NO_F00F_HACK) if ((eva == (unsigned int)&idt[6]) && has_f00f_bug) { *ucode = ILL_PRVOPC; *signo = SIGILL; return (-2); } #endif if (usermode) { *signo = SIGSEGV; *ucode = SEGV_MAPERR; return (1); } trap_fatal(frame, eva); return (-1); } else { map = usermode ? &p->p_vmspace->vm_map : kernel_map; /* * Kernel cannot access a user-space address directly * because user pages are not mapped. Also, page * faults must not be caused during the interrupts. */ if (!usermode && td->td_intr_nesting_level != 0) { trap_fatal(frame, eva); return (-1); } } /* * If the trap was caused by errant bits in the PTE then panic. */ if (frame->tf_err & PGEX_RSV) { trap_fatal(frame, eva); return (-1); } /* * PGEX_I is defined only if the execute disable bit capability is * supported and enabled. */ if (frame->tf_err & PGEX_W) ftype = VM_PROT_WRITE; else if ((frame->tf_err & PGEX_I) && pg_nx != 0) ftype = VM_PROT_EXECUTE; else ftype = VM_PROT_READ; /* Fault in the page. */ rv = vm_fault_trap(map, eva, ftype, VM_FAULT_NORMAL, signo, ucode); if (rv == KERN_SUCCESS) { #ifdef HWPMC_HOOKS if (ftype == VM_PROT_READ || ftype == VM_PROT_WRITE) { PMC_SOFT_CALL_TF( , , page_fault, all, frame); if (ftype == VM_PROT_READ) PMC_SOFT_CALL_TF( , , page_fault, read, frame); else PMC_SOFT_CALL_TF( , , page_fault, write, frame); } #endif return (0); } if (usermode) return (1); if (td->td_intr_nesting_level == 0 && curpcb->pcb_onfault != NULL) { frame->tf_eip = (int)curpcb->pcb_onfault; return (0); } trap_fatal(frame, eva); return (-1); } static void trap_fatal(struct trapframe *frame, vm_offset_t eva) { int code, ss, esp; u_int type; struct soft_segment_descriptor softseg; #ifdef KDB bool handled; #endif code = frame->tf_err; type = frame->tf_trapno; sdtossd(&gdt[IDXSEL(frame->tf_cs & 0xffff)].sd, &softseg); printf("\n\nFatal trap %d: %s while in %s mode\n", type, trap_msg(type), frame->tf_eflags & PSL_VM ? "vm86" : ISPL(frame->tf_cs) == SEL_UPL ? "user" : "kernel"); #ifdef SMP /* two separate prints in case of a trap on an unmapped page */ printf("cpuid = %d; ", PCPU_GET(cpuid)); printf("apic id = %02x\n", PCPU_GET(apic_id)); #endif if (type == T_PAGEFLT) { printf("fault virtual address = 0x%x\n", eva); printf("fault code = %s %s%s, %s\n", code & PGEX_U ? "user" : "supervisor", code & PGEX_W ? "write" : "read", pg_nx != 0 ? (code & PGEX_I ? " instruction" : " data") : "", code & PGEX_RSV ? "reserved bits in PTE" : code & PGEX_P ? "protection violation" : "page not present"); } else { printf("error code = %#x\n", code); } printf("instruction pointer = 0x%x:0x%x\n", frame->tf_cs & 0xffff, frame->tf_eip); if (TF_HAS_STACKREGS(frame)) { ss = frame->tf_ss & 0xffff; esp = frame->tf_esp; } else { ss = GSEL(GDATA_SEL, SEL_KPL); esp = (int)&frame->tf_esp; } printf("stack pointer = 0x%x:0x%x\n", ss, esp); printf("frame pointer = 0x%x:0x%x\n", ss, frame->tf_ebp); printf("code segment = base 0x%x, limit 0x%x, type 0x%x\n", softseg.ssd_base, softseg.ssd_limit, softseg.ssd_type); printf(" = DPL %d, pres %d, def32 %d, gran %d\n", softseg.ssd_dpl, softseg.ssd_p, softseg.ssd_def32, softseg.ssd_gran); printf("processor eflags = "); if (frame->tf_eflags & PSL_T) printf("trace trap, "); if (frame->tf_eflags & PSL_I) printf("interrupt enabled, "); if (frame->tf_eflags & PSL_NT) printf("nested task, "); if (frame->tf_eflags & PSL_RF) printf("resume, "); if (frame->tf_eflags & PSL_VM) printf("vm86, "); printf("IOPL = %d\n", (frame->tf_eflags & PSL_IOPL) >> 12); printf("current process = %d (%s)\n", curproc->p_pid, curthread->td_name); #ifdef KDB if (debugger_on_trap) { kdb_why = KDB_WHY_TRAP; frame->tf_err = eva; /* smuggle fault address to ddb */ handled = kdb_trap(type, 0, frame); frame->tf_err = code; /* restore error code */ kdb_why = KDB_WHY_UNSET; if (handled) return; } #endif printf("trap number = %d\n", type); if (trap_msg(type) != NULL) panic("%s", trap_msg(type)); else panic("unknown/reserved trap"); } #ifdef KDTRACE_HOOKS /* * Invoke a userspace DTrace hook. The hook pointer is cleared when no * userspace probes are enabled, so we must synchronize with DTrace to ensure * that a trapping thread is able to call the hook before it is cleared. */ static bool trap_user_dtrace(struct trapframe *frame, int (**hookp)(struct trapframe *)) { int (*hook)(struct trapframe *); hook = atomic_load_ptr(hookp); enable_intr(); if (hook != NULL) return ((hook)(frame) == 0); return (false); } #endif /* * Double fault handler. Called when a fault occurs while writing * a frame for a trap/exception onto the stack. This usually occurs * when the stack overflows (such is the case with infinite recursion, * for example). * * XXX Note that the current PTD gets replaced by IdlePTD when the * task switch occurs. This means that the stack that was active at * the time of the double fault is not available at unless * the machine was idle when the double fault occurred. The downside * of this is that "trace " in ddb won't work. */ void dblfault_handler(void) { #ifdef KDTRACE_HOOKS if (dtrace_doubletrap_func != NULL) (*dtrace_doubletrap_func)(); #endif printf("\nFatal double fault:\n"); printf("eip = 0x%x\n", PCPU_GET(common_tssp)->tss_eip); printf("esp = 0x%x\n", PCPU_GET(common_tssp)->tss_esp); printf("ebp = 0x%x\n", PCPU_GET(common_tssp)->tss_ebp); #ifdef SMP /* two separate prints in case of a trap on an unmapped page */ printf("cpuid = %d; ", PCPU_GET(cpuid)); printf("apic id = %02x\n", PCPU_GET(apic_id)); #endif panic("double fault"); } int cpu_fetch_syscall_args(struct thread *td) { struct proc *p; struct trapframe *frame; struct syscall_args *sa; caddr_t params; long tmp; int error; #ifdef COMPAT_43 u_int32_t eip; int cs; #endif p = td->td_proc; frame = td->td_frame; sa = &td->td_sa; #ifdef COMPAT_43 if (__predict_false(frame->tf_cs == 7 && frame->tf_eip == 2)) { /* * In lcall $7,$0 after int $0x80. Convert the user * frame to what it would be for a direct int 0x80 instead * of lcall $7,$0, by popping the lcall return address. */ error = fueword32((void *)frame->tf_esp, &eip); if (error == -1) return (EFAULT); cs = fuword16((void *)(frame->tf_esp + sizeof(u_int32_t))); if (cs == -1) return (EFAULT); /* * Unwind in-kernel frame after all stack frame pieces * were successfully read. */ frame->tf_eip = eip; frame->tf_cs = cs; frame->tf_esp += 2 * sizeof(u_int32_t); frame->tf_err = 7; /* size of lcall $7,$0 */ } #endif sa->code = frame->tf_eax; sa->original_code = sa->code; params = (caddr_t)frame->tf_esp + sizeof(uint32_t); /* * Need to check if this is a 32 bit or 64 bit syscall. */ if (sa->code == SYS_syscall) { /* * Code is first argument, followed by actual args. */ error = fueword(params, &tmp); if (error == -1) return (EFAULT); sa->code = tmp; params += sizeof(uint32_t); } else if (sa->code == SYS___syscall) { /* * Like syscall, but code is a quad, so as to maintain * quad alignment for the rest of the arguments. */ error = fueword(params, &tmp); if (error == -1) return (EFAULT); sa->code = tmp; params += sizeof(quad_t); } if (sa->code >= p->p_sysent->sv_size) sa->callp = &p->p_sysent->sv_table[0]; else sa->callp = &p->p_sysent->sv_table[sa->code]; if (params != NULL && sa->callp->sy_narg != 0) error = copyin(params, (caddr_t)sa->args, (u_int)(sa->callp->sy_narg * sizeof(uint32_t))); else error = 0; if (error == 0) { td->td_retval[0] = 0; td->td_retval[1] = frame->tf_edx; } return (error); } #include "../../kern/subr_syscall.c" /* * syscall - system call request C handler. A system call is * essentially treated as a trap by reusing the frame layout. */ void syscall(struct trapframe *frame) { struct thread *td; register_t orig_tf_eflags; ksiginfo_t ksi; #ifdef DIAGNOSTIC if (!(TRAPF_USERMODE(frame) && (curpcb->pcb_flags & PCB_VM86CALL) == 0)) { panic("syscall"); /* NOT REACHED */ } #endif orig_tf_eflags = frame->tf_eflags; td = curthread; td->td_frame = frame; syscallenter(td); /* * Traced syscall. */ if ((orig_tf_eflags & PSL_T) && !(orig_tf_eflags & PSL_VM)) { frame->tf_eflags &= ~PSL_T; ksiginfo_init_trap(&ksi); ksi.ksi_signo = SIGTRAP; ksi.ksi_code = TRAP_TRACE; ksi.ksi_addr = (void *)frame->tf_eip; trapsignal(td, &ksi); } KASSERT(PCB_USER_FPU(td->td_pcb), ("System call %s returning with kernel FPU ctx leaked", syscallname(td->td_proc, td->td_sa.code))); KASSERT(td->td_pcb->pcb_save == get_pcb_user_save_td(td), ("System call %s returning with mangled pcb_save", syscallname(td->td_proc, td->td_sa.code))); syscallret(td); } diff --git a/sys/kern/kern_cons.c b/sys/kern/kern_cons.c index 780fce00387d..d33811f1e3c8 100644 --- a/sys/kern/kern_cons.c +++ b/sys/kern/kern_cons.c @@ -1,770 +1,771 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1988 University of Utah. * Copyright (c) 1991 The Regents of the University of California. * Copyright (c) 1999 Michael Smith * Copyright (c) 2005 Pawel Jakub Dawidek * * All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department. * * 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. * 3. 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. * * from: @(#)cons.c 7.2 (Berkeley) 5/9/91 */ #include __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #include "opt_syscons.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_TTYCONS, "tty console", "tty console handling"); struct cn_device { STAILQ_ENTRY(cn_device) cnd_next; struct consdev *cnd_cn; }; #define CNDEVPATHMAX 32 #define CNDEVTAB_SIZE 4 static struct cn_device cn_devtab[CNDEVTAB_SIZE]; static STAILQ_HEAD(, cn_device) cn_devlist = STAILQ_HEAD_INITIALIZER(cn_devlist); int cons_avail_mask = 0; /* Bit mask. Each registered low level console * which is currently unavailable for inpit * (i.e., if it is in graphics mode) will have * this bit cleared. */ static int cn_mute; SYSCTL_INT(_kern, OID_AUTO, consmute, CTLFLAG_RW, &cn_mute, 0, "State of the console muting"); static char *consbuf; /* buffer used by `consmsgbuf' */ static struct callout conscallout; /* callout for outputting to constty */ struct msgbuf consmsgbuf; /* message buffer for console tty */ static u_char console_pausing; /* pause after each line during probe */ static const char console_pausestr[] = ""; struct tty *constty; /* pointer to console "window" tty */ static struct mtx constty_mtx; /* Mutex for constty assignment. */ MTX_SYSINIT(constty_mtx, &constty_mtx, "constty_mtx", MTX_DEF); static struct mtx cnputs_mtx; /* Mutex for cnputs(). */ MTX_SYSINIT(cnputs_mtx, &cnputs_mtx, "cnputs_mtx", MTX_SPIN | MTX_NOWITNESS); static void constty_timeout(void *arg); static struct consdev cons_consdev; DATA_SET(cons_set, cons_consdev); SET_DECLARE(cons_set, struct consdev); /* * Stub for configurations that don't actually have a keyboard driver. Inclusion * of kbd.c is contingent on any number of keyboard/console drivers being * present in the kernel; rather than trying to catch them all, we'll just * maintain this weak kbdinit that will be overridden by the strong version in * kbd.c if it's present. */ __weak_symbol void kbdinit(void) { } void cninit(void) { struct consdev *best_cn, *cn, **list; /* * Check if we should mute the console (for security reasons perhaps) * It can be changes dynamically using sysctl kern.consmute * once we are up and going. * */ cn_mute = ((boothowto & (RB_MUTE |RB_SINGLE |RB_VERBOSE |RB_ASKNAME)) == RB_MUTE); /* * Bring up the kbd layer just in time for cnprobe. Console drivers * have a dependency on kbd being ready, so this fits nicely between the * machdep callers of cninit() and MI probing/initialization of consoles * here. */ kbdinit(); /* * Find the first console with the highest priority. */ best_cn = NULL; SET_FOREACH(list, cons_set) { cn = *list; cnremove(cn); /* Skip cons_consdev. */ if (cn->cn_ops == NULL) continue; cn->cn_ops->cn_probe(cn); if (cn->cn_pri == CN_DEAD) continue; if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri) best_cn = cn; if (boothowto & RB_MULTIPLE) { /* * Initialize console, and attach to it. */ cn->cn_ops->cn_init(cn); cnadd(cn); } } if (best_cn == NULL) return; if ((boothowto & RB_MULTIPLE) == 0) { best_cn->cn_ops->cn_init(best_cn); cnadd(best_cn); } if (boothowto & RB_PAUSE) console_pausing = 1; /* * Make the best console the preferred console. */ cnselect(best_cn); #ifdef EARLY_PRINTF /* * Release early console. */ early_putc = NULL; #endif } void cninit_finish() { console_pausing = 0; } /* add a new physical console to back the virtual console */ int cnadd(struct consdev *cn) { struct cn_device *cnd; int i; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) if (cnd->cnd_cn == cn) return (0); for (i = 0; i < CNDEVTAB_SIZE; i++) { cnd = &cn_devtab[i]; if (cnd->cnd_cn == NULL) break; } if (cnd->cnd_cn != NULL) return (ENOMEM); cnd->cnd_cn = cn; if (cn->cn_name[0] == '\0') { /* XXX: it is unclear if/where this print might output */ printf("WARNING: console at %p has no name\n", cn); } STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next); if (STAILQ_FIRST(&cn_devlist) == cnd) ttyconsdev_select(cnd->cnd_cn->cn_name); /* Add device to the active mask. */ cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0); return (0); } void cnremove(struct consdev *cn) { struct cn_device *cnd; int i; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { if (cnd->cnd_cn != cn) continue; if (STAILQ_FIRST(&cn_devlist) == cnd) ttyconsdev_select(NULL); STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); cnd->cnd_cn = NULL; /* Remove this device from available mask. */ for (i = 0; i < CNDEVTAB_SIZE; i++) if (cnd == &cn_devtab[i]) { cons_avail_mask &= ~(1 << i); break; } #if 0 /* * XXX * syscons gets really confused if console resources are * freed after the system has initialized. */ if (cn->cn_term != NULL) cn->cn_ops->cn_term(cn); #endif return; } } void cnselect(struct consdev *cn) { struct cn_device *cnd; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { if (cnd->cnd_cn != cn) continue; if (cnd == STAILQ_FIRST(&cn_devlist)) return; STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next); ttyconsdev_select(cnd->cnd_cn->cn_name); return; } } void cnavailable(struct consdev *cn, int available) { int i; for (i = 0; i < CNDEVTAB_SIZE; i++) { if (cn_devtab[i].cnd_cn == cn) break; } if (available) { if (i < CNDEVTAB_SIZE) cons_avail_mask |= (1 << i); cn->cn_flags &= ~CN_FLAG_NOAVAIL; } else { if (i < CNDEVTAB_SIZE) cons_avail_mask &= ~(1 << i); cn->cn_flags |= CN_FLAG_NOAVAIL; } } int cnunavailable(void) { return (cons_avail_mask == 0); } /* * sysctl_kern_console() provides output parseable in conscontrol(1). */ static int sysctl_kern_console(SYSCTL_HANDLER_ARGS) { struct cn_device *cnd; struct consdev *cp, **list; char *p; int delete, error; struct sbuf *sb; sb = sbuf_new(NULL, NULL, CNDEVPATHMAX * 2, SBUF_AUTOEXTEND | SBUF_INCLUDENUL); if (sb == NULL) return (ENOMEM); sbuf_clear(sb); STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) sbuf_printf(sb, "%s,", cnd->cnd_cn->cn_name); sbuf_printf(sb, "/"); SET_FOREACH(list, cons_set) { cp = *list; if (cp->cn_name[0] != '\0') sbuf_printf(sb, "%s,", cp->cn_name); } sbuf_finish(sb); error = sysctl_handle_string(oidp, sbuf_data(sb), sbuf_len(sb), req); if (error == 0 && req->newptr != NULL) { p = sbuf_data(sb); error = ENXIO; delete = 0; if (*p == '-') { delete = 1; p++; } SET_FOREACH(list, cons_set) { cp = *list; if (strcmp(p, cp->cn_name) != 0) continue; if (delete) { cnremove(cp); error = 0; } else { error = cnadd(cp); if (error == 0) cnselect(cp); } break; } } sbuf_delete(sb); return (error); } SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, 0, sysctl_kern_console, "A", "Console device control"); void cngrab() { struct cn_device *cnd; struct consdev *cn; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) cn->cn_ops->cn_grab(cn); } } void cnungrab() { struct cn_device *cnd; struct consdev *cn; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) cn->cn_ops->cn_ungrab(cn); } } void cnresume() { struct cn_device *cnd; struct consdev *cn; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (cn->cn_ops->cn_resume != NULL) cn->cn_ops->cn_resume(cn); } } /* * Low level console routines. */ int cngetc(void) { int c; if (cn_mute) return (-1); while ((c = cncheckc()) == -1) cpu_spinwait(); if (c == '\r') c = '\n'; /* console input is always ICRNL */ return (c); } int cncheckc(void) { struct cn_device *cnd; struct consdev *cn; int c; if (cn_mute) return (-1); STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { c = cn->cn_ops->cn_getc(cn); if (c != -1) return (c); } } return (-1); } void cngets(char *cp, size_t size, int visible) { char *lp, *end; int c; cngrab(); lp = cp; end = cp + size - 1; for (;;) { c = cngetc() & 0177; switch (c) { case '\n': case '\r': cnputc(c); *lp = '\0'; cnungrab(); return; case '\b': case '\177': if (lp > cp) { if (visible) cnputs("\b \b"); lp--; } continue; case '\0': continue; default: if (lp < end) { switch (visible) { case GETS_NOECHO: break; case GETS_ECHOPASS: cnputc('*'); break; default: cnputc(c); break; } *lp++ = c; } } } } void cnputc(int c) { struct cn_device *cnd; struct consdev *cn; const char *cp; #ifdef EARLY_PRINTF if (early_putc != NULL) { if (c == '\n') early_putc('\r'); early_putc(c); return; } #endif if (cn_mute || c == '\0') return; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { if (c == '\n') cn->cn_ops->cn_putc(cn, '\r'); cn->cn_ops->cn_putc(cn, c); } } if (console_pausing && c == '\n' && !kdb_active) { for (cp = console_pausestr; *cp != '\0'; cp++) cnputc(*cp); cngrab(); if (cngetc() == '.') console_pausing = 0; cnungrab(); cnputc('\r'); for (cp = console_pausestr; *cp != '\0'; cp++) cnputc(' '); cnputc('\r'); } } void cnputsn(const char *p, size_t n) { size_t i; int unlock_reqd = 0; if (mtx_initialized(&cnputs_mtx)) { /* * NOTE: Debug prints and/or witness printouts in * console driver clients can cause the "cnputs_mtx" * mutex to recurse. Simply return if that happens. */ if (mtx_owned(&cnputs_mtx)) return; mtx_lock_spin(&cnputs_mtx); unlock_reqd = 1; } for (i = 0; i < n; i++) cnputc(p[i]); if (unlock_reqd) mtx_unlock_spin(&cnputs_mtx); } void cnputs(const char *p) { cnputsn(p, strlen(p)); } static unsigned int consmsgbuf_size = 65536; SYSCTL_UINT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RWTUN, &consmsgbuf_size, 0, "Console tty buffer size"); /* * Redirect console output to a tty. */ int constty_set(struct tty *tp) { int size = consmsgbuf_size; void *buf = NULL; tty_assert_locked(tp); if (constty == tp) return (0); if (constty != NULL) return (EBUSY); if (consbuf == NULL) { tty_unlock(tp); buf = malloc(size, M_TTYCONS, M_WAITOK); tty_lock(tp); } mtx_lock(&constty_mtx); if (constty != NULL) { mtx_unlock(&constty_mtx); free(buf, M_TTYCONS); return (EBUSY); } if (consbuf == NULL) { consbuf = buf; msgbuf_init(&consmsgbuf, buf, size); } else free(buf, M_TTYCONS); constty = tp; mtx_unlock(&constty_mtx); callout_init_mtx(&conscallout, tty_getlock(tp), 0); constty_timeout(tp); return (0); } /* * Disable console redirection to a tty. */ int constty_clear(struct tty *tp) { int c; tty_assert_locked(tp); if (constty != tp) return (ENXIO); callout_stop(&conscallout); mtx_lock(&constty_mtx); constty = NULL; mtx_unlock(&constty_mtx); while ((c = msgbuf_getchar(&consmsgbuf)) != -1) cnputc(c); /* We never free consbuf because it can still be in use. */ return (0); } /* Times per second to check for pending console tty messages. */ static int constty_wakeups_per_second = 15; SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW, &constty_wakeups_per_second, 0, "Times per second to check for pending console tty messages"); static void constty_timeout(void *arg) { struct tty *tp = arg; int c; tty_assert_locked(tp); while ((c = msgbuf_getchar(&consmsgbuf)) != -1) { if (tty_putchar(tp, c) < 0) { constty_clear(tp); return; } } callout_reset_sbt(&conscallout, SBT_1S / constty_wakeups_per_second, 0, constty_timeout, tp, C_PREL(1)); } /* * Sysbeep(), if we have hardware for it */ #ifdef HAS_TIMER_SPKR -static int beeping; +static bool beeping; static struct callout beeping_timer; static void sysbeepstop(void *chan) { timer_spkr_release(); - beeping = 0; + beeping = false; } int -sysbeep(int pitch, int period) +sysbeep(int pitch, sbintime_t duration) { if (timer_spkr_acquire()) { if (!beeping) { /* Something else owns it. */ return (EBUSY); } } timer_spkr_setfreq(pitch); if (!beeping) { - beeping = period; - callout_reset(&beeping_timer, period, sysbeepstop, NULL); + beeping = true; + callout_reset_sbt(&beeping_timer, duration, 0, sysbeepstop, + NULL, C_PREL(5)); } return (0); } static void sysbeep_init(void *unused) { callout_init(&beeping_timer, 1); } SYSINIT(sysbeep, SI_SUB_SOFTINTR, SI_ORDER_ANY, sysbeep_init, NULL); #else /* * No hardware, no sound */ int -sysbeep(int pitch __unused, int period __unused) +sysbeep(int pitch __unused, sbintime_t duration __unused) { return (ENODEV); } #endif /* * Temporary support for sc(4) to vt(4) transition. */ static unsigned vty_prefer; static char vty_name[16]; SYSCTL_STRING(_kern, OID_AUTO, vty, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, vty_name, 0, "Console vty driver"); int vty_enabled(unsigned vty) { static unsigned vty_selected = 0; if (vty_selected == 0) { TUNABLE_STR_FETCH("kern.vty", vty_name, sizeof(vty_name)); do { #if defined(DEV_SC) if (strcmp(vty_name, "sc") == 0) { vty_selected = VTY_SC; break; } #endif #if defined(DEV_VT) if (strcmp(vty_name, "vt") == 0) { vty_selected = VTY_VT; break; } #endif if (vty_prefer != 0) { vty_selected = vty_prefer; break; } #if defined(DEV_VT) vty_selected = VTY_VT; #elif defined(DEV_SC) vty_selected = VTY_SC; #endif } while (0); if (vty_selected == VTY_VT) strcpy(vty_name, "vt"); else if (vty_selected == VTY_SC) strcpy(vty_name, "sc"); } return ((vty_selected & vty) != 0); } void vty_set_preferred(unsigned vty) { vty_prefer = vty; #if !defined(DEV_SC) vty_prefer &= ~VTY_SC; #endif #if !defined(DEV_VT) vty_prefer &= ~VTY_VT; #endif } diff --git a/sys/sys/systm.h b/sys/sys/systm.h index 497e09f86488..ffe014eb8b42 100644 --- a/sys/sys/systm.h +++ b/sys/sys/systm.h @@ -1,672 +1,672 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1982, 1988, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * 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. * 3. 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. * * @(#)systm.h 8.7 (Berkeley) 3/29/95 * $FreeBSD$ */ #ifndef _SYS_SYSTM_H_ #define _SYS_SYSTM_H_ #include #include #include #include #include #include /* for people using printf mainly */ __NULLABILITY_PRAGMA_PUSH #ifdef _KERNEL extern int cold; /* nonzero if we are doing a cold boot */ extern int suspend_blocked; /* block suspend due to pending shutdown */ extern int rebooting; /* kern_reboot() has been called. */ extern const char *panicstr; /* panic message */ extern bool panicked; #define KERNEL_PANICKED() __predict_false(panicked) extern char version[]; /* system version */ extern char compiler_version[]; /* compiler version */ extern char copyright[]; /* system copyright */ extern int kstack_pages; /* number of kernel stack pages */ extern u_long pagesizes[]; /* supported page sizes */ extern long physmem; /* physical memory */ extern long realmem; /* 'real' memory */ extern char *rootdevnames[2]; /* names of possible root devices */ extern int boothowto; /* reboot flags, from console subsystem */ extern int bootverbose; /* nonzero to print verbose messages */ extern int maxusers; /* system tune hint */ extern int ngroups_max; /* max # of supplemental groups */ extern int vm_guest; /* Running as virtual machine guest? */ extern u_long maxphys; /* max raw I/O transfer size */ /* * Detected virtual machine guest types. The intention is to expand * and/or add to the VM_GUEST_VM type if specific VM functionality is * ever implemented (e.g. vendor-specific paravirtualization features). * Keep in sync with vm_guest_sysctl_names[]. */ enum VM_GUEST { VM_GUEST_NO = 0, VM_GUEST_VM, VM_GUEST_XEN, VM_GUEST_HV, VM_GUEST_VMWARE, VM_GUEST_KVM, VM_GUEST_BHYVE, VM_GUEST_VBOX, VM_GUEST_PARALLELS, VM_LAST }; #ifdef INVARIANTS /* The option is always available */ #define VNASSERT(exp, vp, msg) do { \ if (__predict_false(!(exp))) { \ vn_printf(vp, "VNASSERT failed: %s not true at %s:%d (%s)\n",\ #exp, __FILE__, __LINE__, __func__); \ kassert_panic msg; \ } \ } while (0) #define VNPASS(exp, vp) do { \ const char *_exp = #exp; \ VNASSERT(exp, vp, ("condition %s not met at %s:%d (%s)", \ _exp, __FILE__, __LINE__, __func__)); \ } while (0) #define __assert_unreachable() \ panic("executing segment marked as unreachable at %s:%d (%s)\n", \ __FILE__, __LINE__, __func__) #else #define VNASSERT(exp, vp, msg) do { \ } while (0) #define VNPASS(exp, vp) do { \ } while (0) #define __assert_unreachable() __unreachable() #endif #ifndef CTASSERT /* Allow lint to override */ #define CTASSERT(x) _Static_assert(x, "compile-time assertion failed") #endif #endif /* KERNEL */ /* * These functions need to be declared before the KASSERT macro is invoked in * !KASSERT_PANIC_OPTIONAL builds, so their declarations are sort of out of * place compared to other function definitions in this header. On the other * hand, this header is a bit disorganized anyway. */ void panic(const char *, ...) __dead2 __printflike(1, 2); void vpanic(const char *, __va_list) __dead2 __printflike(1, 0); #if defined(_STANDALONE) struct ucred; /* * Until we have more experience with KASSERTS that are called * from the boot loader, they are off. The bootloader does this * a little differently than the kernel (we just call printf atm). * we avoid most of the common functions in the boot loader, so * declare printf() here too. */ int printf(const char *, ...) __printflike(1, 2); # define kassert_panic printf #else /* !_STANDALONE */ # if defined(WITNESS) || defined(INVARIANT_SUPPORT) # ifdef KASSERT_PANIC_OPTIONAL void kassert_panic(const char *fmt, ...) __printflike(1, 2); # else # define kassert_panic panic # endif /* KASSERT_PANIC_OPTIONAL */ # endif /* defined(WITNESS) || defined(INVARIANT_SUPPORT) */ #endif /* _STANDALONE */ #if defined(INVARIANTS) || defined(_STANDALONE) #define KASSERT(exp,msg) do { \ if (__predict_false(!(exp))) \ kassert_panic msg; \ } while (0) #else /* !INVARIANTS && !_STANDALONE */ #define KASSERT(exp,msg) do { \ } while (0) #endif /* INVARIANTS || _STANDALONE */ /* * Helpful macros for quickly coming up with assertions with informative * panic messages. */ #define MPASS(ex) MPASS4(ex, #ex, __FILE__, __LINE__) #define MPASS2(ex, what) MPASS4(ex, what, __FILE__, __LINE__) #define MPASS3(ex, file, line) MPASS4(ex, #ex, file, line) #define MPASS4(ex, what, file, line) \ KASSERT((ex), ("Assertion %s failed at %s:%d", what, file, line)) /* * Align variables. */ #define __read_mostly __section(".data.read_mostly") #define __read_frequently __section(".data.read_frequently") #define __exclusive_cache_line __aligned(CACHE_LINE_SIZE) \ __section(".data.exclusive_cache_line") #ifdef _KERNEL #include /* MAXCPU */ #include /* curthread */ #include /* * Assert that a pointer can be loaded from memory atomically. * * This assertion enforces stronger alignment than necessary. For example, * on some architectures, atomicity for unaligned loads will depend on * whether or not the load spans multiple cache lines. */ #define ASSERT_ATOMIC_LOAD_PTR(var, msg) \ KASSERT(sizeof(var) == sizeof(void *) && \ ((uintptr_t)&(var) & (sizeof(void *) - 1)) == 0, msg) /* * Assert that a thread is in critical(9) section. */ #define CRITICAL_ASSERT(td) \ KASSERT((td)->td_critnest >= 1, ("Not in critical section")) /* * If we have already panic'd and this is the thread that called * panic(), then don't block on any mutexes but silently succeed. * Otherwise, the kernel will deadlock since the scheduler isn't * going to run the thread that holds any lock we need. */ #define SCHEDULER_STOPPED_TD(td) ({ \ MPASS((td) == curthread); \ __predict_false((td)->td_stopsched); \ }) #define SCHEDULER_STOPPED() SCHEDULER_STOPPED_TD(curthread) extern int osreldate; extern const void *zero_region; /* address space maps to a zeroed page */ extern int unmapped_buf_allowed; #ifdef __LP64__ #define IOSIZE_MAX iosize_max() #define DEVFS_IOSIZE_MAX devfs_iosize_max() #else #define IOSIZE_MAX SSIZE_MAX #define DEVFS_IOSIZE_MAX SSIZE_MAX #endif /* * General function declarations. */ struct inpcb; struct lock_object; struct malloc_type; struct mtx; struct proc; struct socket; struct thread; struct tty; struct ucred; struct uio; struct _jmp_buf; struct trapframe; struct eventtimer; int setjmp(struct _jmp_buf *) __returns_twice; void longjmp(struct _jmp_buf *, int) __dead2; int dumpstatus(vm_offset_t addr, off_t count); int nullop(void); int eopnotsupp(void); int ureadc(int, struct uio *); void hashdestroy(void *, struct malloc_type *, u_long); void *hashinit(int count, struct malloc_type *type, u_long *hashmask); void *hashinit_flags(int count, struct malloc_type *type, u_long *hashmask, int flags); #define HASH_NOWAIT 0x00000001 #define HASH_WAITOK 0x00000002 void *phashinit(int count, struct malloc_type *type, u_long *nentries); void *phashinit_flags(int count, struct malloc_type *type, u_long *nentries, int flags); void g_waitidle(void); void cpu_flush_dcache(void *, size_t); void cpu_rootconf(void); void critical_enter_KBI(void); void critical_exit_KBI(void); void critical_exit_preempt(void); void init_param1(void); void init_param2(long physpages); void init_static_kenv(char *, size_t); void tablefull(const char *); /* * Allocate per-thread "current" state in the linuxkpi */ extern int (*lkpi_alloc_current)(struct thread *, int); int linux_alloc_current_noop(struct thread *, int); #if (defined(KLD_MODULE) && !defined(KLD_TIED)) || defined(KTR_CRITICAL) || !defined(_KERNEL) || defined(GENOFFSET) #define critical_enter() critical_enter_KBI() #define critical_exit() critical_exit_KBI() #else static __inline void critical_enter(void) { struct thread_lite *td; td = (struct thread_lite *)curthread; td->td_critnest++; atomic_interrupt_fence(); } static __inline void critical_exit(void) { struct thread_lite *td; td = (struct thread_lite *)curthread; KASSERT(td->td_critnest != 0, ("critical_exit: td_critnest == 0")); atomic_interrupt_fence(); td->td_critnest--; atomic_interrupt_fence(); if (__predict_false(td->td_owepreempt)) critical_exit_preempt(); } #endif #ifdef EARLY_PRINTF typedef void early_putc_t(int ch); extern early_putc_t *early_putc; #endif int kvprintf(char const *, void (*)(int, void*), void *, int, __va_list) __printflike(1, 0); void log(int, const char *, ...) __printflike(2, 3); void log_console(struct uio *); void vlog(int, const char *, __va_list) __printflike(2, 0); int asprintf(char **ret, struct malloc_type *mtp, const char *format, ...) __printflike(3, 4); int printf(const char *, ...) __printflike(1, 2); int snprintf(char *, size_t, const char *, ...) __printflike(3, 4); int sprintf(char *buf, const char *, ...) __printflike(2, 3); int uprintf(const char *, ...) __printflike(1, 2); int vprintf(const char *, __va_list) __printflike(1, 0); int vasprintf(char **ret, struct malloc_type *mtp, const char *format, __va_list ap) __printflike(3, 0); int vsnprintf(char *, size_t, const char *, __va_list) __printflike(3, 0); int vsnrprintf(char *, size_t, int, const char *, __va_list) __printflike(4, 0); int vsprintf(char *buf, const char *, __va_list) __printflike(2, 0); int sscanf(const char *, char const * _Nonnull, ...) __scanflike(2, 3); int vsscanf(const char * _Nonnull, char const * _Nonnull, __va_list) __scanflike(2, 0); long strtol(const char *, char **, int); u_long strtoul(const char *, char **, int); quad_t strtoq(const char *, char **, int); u_quad_t strtouq(const char *, char **, int); void tprintf(struct proc *p, int pri, const char *, ...) __printflike(3, 4); void vtprintf(struct proc *, int, const char *, __va_list) __printflike(3, 0); void hexdump(const void *ptr, int length, const char *hdr, int flags); #define HD_COLUMN_MASK 0xff #define HD_DELIM_MASK 0xff00 #define HD_OMIT_COUNT (1 << 16) #define HD_OMIT_HEX (1 << 17) #define HD_OMIT_CHARS (1 << 18) #define ovbcopy(f, t, l) bcopy((f), (t), (l)) void explicit_bzero(void * _Nonnull, size_t); void *memset(void * _Nonnull buf, int c, size_t len); void *memcpy(void * _Nonnull to, const void * _Nonnull from, size_t len); void *memmove(void * _Nonnull dest, const void * _Nonnull src, size_t n); int memcmp(const void *b1, const void *b2, size_t len); #ifdef SAN_NEEDS_INTERCEPTORS #define SAN_INTERCEPTOR(func) \ __CONCAT(SAN_INTERCEPTOR_PREFIX, __CONCAT(_, func)) void *SAN_INTERCEPTOR(memset)(void *, int, size_t); void *SAN_INTERCEPTOR(memcpy)(void *, const void *, size_t); void *SAN_INTERCEPTOR(memmove)(void *, const void *, size_t); int SAN_INTERCEPTOR(memcmp)(const void *, const void *, size_t); #ifndef SAN_RUNTIME #define bcopy(from, to, len) SAN_INTERCEPTOR(memmove)((to), (from), (len)) #define bzero(buf, len) SAN_INTERCEPTOR(memset)((buf), 0, (len)) #define bcmp(b1, b2, len) SAN_INTERCEPTOR(memcmp)((b1), (b2), (len)) #define memset(buf, c, len) SAN_INTERCEPTOR(memset)((buf), (c), (len)) #define memcpy(to, from, len) SAN_INTERCEPTOR(memcpy)((to), (from), (len)) #define memmove(dest, src, n) SAN_INTERCEPTOR(memmove)((dest), (src), (n)) #define memcmp(b1, b2, len) SAN_INTERCEPTOR(memcmp)((b1), (b2), (len)) #endif /* !SAN_RUNTIME */ #else /* !SAN_NEEDS_INTERCEPTORS */ #define bcopy(from, to, len) __builtin_memmove((to), (from), (len)) #define bzero(buf, len) __builtin_memset((buf), 0, (len)) #define bcmp(b1, b2, len) __builtin_memcmp((b1), (b2), (len)) #define memset(buf, c, len) __builtin_memset((buf), (c), (len)) #define memcpy(to, from, len) __builtin_memcpy((to), (from), (len)) #define memmove(dest, src, n) __builtin_memmove((dest), (src), (n)) #define memcmp(b1, b2, len) __builtin_memcmp((b1), (b2), (len)) #endif /* SAN_NEEDS_INTERCEPTORS */ void *memset_early(void * _Nonnull buf, int c, size_t len); #define bzero_early(buf, len) memset_early((buf), 0, (len)) void *memcpy_early(void * _Nonnull to, const void * _Nonnull from, size_t len); void *memmove_early(void * _Nonnull dest, const void * _Nonnull src, size_t n); #define bcopy_early(from, to, len) memmove_early((to), (from), (len)) #define copystr(src, dst, len, outlen) ({ \ size_t __r, __len, *__outlen; \ \ __len = (len); \ __outlen = (outlen); \ __r = strlcpy((dst), (src), __len); \ if (__outlen != NULL) \ *__outlen = ((__r >= __len) ? __len : __r + 1); \ ((__r >= __len) ? ENAMETOOLONG : 0); \ }) int copyinstr(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len, size_t * __restrict lencopied); int copyin(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len); int copyin_nofault(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len); int copyout(const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len); int copyout_nofault(const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len); #ifdef SAN_NEEDS_INTERCEPTORS int SAN_INTERCEPTOR(copyin)(const void *, void *, size_t); int SAN_INTERCEPTOR(copyinstr)(const void *, void *, size_t, size_t *); int SAN_INTERCEPTOR(copyout)(const void *, void *, size_t); #ifndef SAN_RUNTIME #define copyin(u, k, l) SAN_INTERCEPTOR(copyin)((u), (k), (l)) #define copyinstr(u, k, l, lc) SAN_INTERCEPTOR(copyinstr)((u), (k), (l), (lc)) #define copyout(k, u, l) SAN_INTERCEPTOR(copyout)((k), (u), (l)) #endif /* !SAN_RUNTIME */ #endif /* SAN_NEEDS_INTERCEPTORS */ int fubyte(volatile const void *base); long fuword(volatile const void *base); int fuword16(volatile const void *base); int32_t fuword32(volatile const void *base); int64_t fuword64(volatile const void *base); int fueword(volatile const void *base, long *val); int fueword32(volatile const void *base, int32_t *val); int fueword64(volatile const void *base, int64_t *val); int subyte(volatile void *base, int byte); int suword(volatile void *base, long word); int suword16(volatile void *base, int word); int suword32(volatile void *base, int32_t word); int suword64(volatile void *base, int64_t word); uint32_t casuword32(volatile uint32_t *base, uint32_t oldval, uint32_t newval); u_long casuword(volatile u_long *p, u_long oldval, u_long newval); int casueword32(volatile uint32_t *base, uint32_t oldval, uint32_t *oldvalp, uint32_t newval); int casueword(volatile u_long *p, u_long oldval, u_long *oldvalp, u_long newval); #if defined(SAN_NEEDS_INTERCEPTORS) && !defined(KCSAN) int SAN_INTERCEPTOR(fubyte)(volatile const void *base); int SAN_INTERCEPTOR(fuword16)(volatile const void *base); int SAN_INTERCEPTOR(fueword)(volatile const void *base, long *val); int SAN_INTERCEPTOR(fueword32)(volatile const void *base, int32_t *val); int SAN_INTERCEPTOR(fueword64)(volatile const void *base, int64_t *val); int SAN_INTERCEPTOR(subyte)(volatile void *base, int byte); int SAN_INTERCEPTOR(suword)(volatile void *base, long word); int SAN_INTERCEPTOR(suword16)(volatile void *base, int word); int SAN_INTERCEPTOR(suword32)(volatile void *base, int32_t word); int SAN_INTERCEPTOR(suword64)(volatile void *base, int64_t word); int SAN_INTERCEPTOR(casueword32)(volatile uint32_t *base, uint32_t oldval, uint32_t *oldvalp, uint32_t newval); int SAN_INTERCEPTOR(casueword)(volatile u_long *p, u_long oldval, u_long *oldvalp, u_long newval); #ifndef SAN_RUNTIME #define fubyte(b) SAN_INTERCEPTOR(fubyte)((b)) #define fuword16(b) SAN_INTERCEPTOR(fuword16)((b)) #define fueword(b, v) SAN_INTERCEPTOR(fueword)((b), (v)) #define fueword32(b, v) SAN_INTERCEPTOR(fueword32)((b), (v)) #define fueword64(b, v) SAN_INTERCEPTOR(fueword64)((b), (v)) #define subyte(b, w) SAN_INTERCEPTOR(subyte)((b), (w)) #define suword(b, w) SAN_INTERCEPTOR(suword)((b), (w)) #define suword16(b, w) SAN_INTERCEPTOR(suword16)((b), (w)) #define suword32(b, w) SAN_INTERCEPTOR(suword32)((b), (w)) #define suword64(b, w) SAN_INTERCEPTOR(suword64)((b), (w)) #define casueword32(b, o, p, n) SAN_INTERCEPTOR(casueword32)((b), (o), (p), (n)) #define casueword(b, o, p, n) SAN_INTERCEPTOR(casueword)((b), (o), (p), (n)) #endif /* !SAN_RUNTIME */ #endif /* SAN_NEEDS_INTERCEPTORS && !KCSAN */ void realitexpire(void *); -int sysbeep(int hertz, int period); +int sysbeep(int hertz, sbintime_t duration); void hardclock(int cnt, int usermode); void hardclock_sync(int cpu); void softclock(void *); void statclock(int cnt, int usermode); void profclock(int cnt, int usermode, uintfptr_t pc); int hardclockintr(void); void startprofclock(struct proc *); void stopprofclock(struct proc *); void cpu_startprofclock(void); void cpu_stopprofclock(void); void suspendclock(void); void resumeclock(void); sbintime_t cpu_idleclock(void); void cpu_activeclock(void); void cpu_new_callout(int cpu, sbintime_t bt, sbintime_t bt_opt); void cpu_et_frequency(struct eventtimer *et, uint64_t newfreq); extern int cpu_disable_c2_sleep; extern int cpu_disable_c3_sleep; char *kern_getenv(const char *name); void freeenv(char *env); int getenv_int(const char *name, int *data); int getenv_uint(const char *name, unsigned int *data); int getenv_long(const char *name, long *data); int getenv_ulong(const char *name, unsigned long *data); int getenv_string(const char *name, char *data, int size); int getenv_int64(const char *name, int64_t *data); int getenv_uint64(const char *name, uint64_t *data); int getenv_quad(const char *name, quad_t *data); int getenv_bool(const char *name, bool *data); bool getenv_is_true(const char *name); bool getenv_is_false(const char *name); int kern_setenv(const char *name, const char *value); int kern_unsetenv(const char *name); int testenv(const char *name); int getenv_array(const char *name, void *data, int size, int *psize, int type_size, bool allow_signed); #define GETENV_UNSIGNED false /* negative numbers not allowed */ #define GETENV_SIGNED true /* negative numbers allowed */ typedef uint64_t (cpu_tick_f)(void); void set_cputicker(cpu_tick_f *func, uint64_t freq, unsigned var); extern cpu_tick_f *cpu_ticks; uint64_t cpu_tickrate(void); uint64_t cputick2usec(uint64_t tick); #include /* Initialize the world */ void consinit(void); void cpu_initclocks(void); void cpu_initclocks_bsp(void); void cpu_initclocks_ap(void); void usrinfoinit(void); /* Finalize the world */ void kern_reboot(int) __dead2; void shutdown_nice(int); /* Stubs for obsolete functions that used to be for interrupt management */ static __inline intrmask_t splhigh(void) { return 0; } static __inline intrmask_t splimp(void) { return 0; } static __inline intrmask_t splnet(void) { return 0; } static __inline intrmask_t spltty(void) { return 0; } static __inline void splx(intrmask_t ipl __unused) { return; } /* * Common `proc' functions are declared here so that proc.h can be included * less often. */ int _sleep(const void * _Nonnull chan, struct lock_object *lock, int pri, const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define msleep(chan, mtx, pri, wmesg, timo) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), \ tick_sbt * (timo), 0, C_HARDCLOCK) #define msleep_sbt(chan, mtx, pri, wmesg, bt, pr, flags) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), (bt), (pr), \ (flags)) int msleep_spin_sbt(const void * _Nonnull chan, struct mtx *mtx, const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define msleep_spin(chan, mtx, wmesg, timo) \ msleep_spin_sbt((chan), (mtx), (wmesg), tick_sbt * (timo), \ 0, C_HARDCLOCK) int pause_sbt(const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define pause(wmesg, timo) \ pause_sbt((wmesg), tick_sbt * (timo), 0, C_HARDCLOCK) #define pause_sig(wmesg, timo) \ pause_sbt((wmesg), tick_sbt * (timo), 0, C_HARDCLOCK | C_CATCH) #define tsleep(chan, pri, wmesg, timo) \ _sleep((chan), NULL, (pri), (wmesg), tick_sbt * (timo), \ 0, C_HARDCLOCK) #define tsleep_sbt(chan, pri, wmesg, bt, pr, flags) \ _sleep((chan), NULL, (pri), (wmesg), (bt), (pr), (flags)) void wakeup(const void *chan); void wakeup_one(const void *chan); void wakeup_any(const void *chan); /* * Common `struct cdev *' stuff are declared here to avoid #include poisoning */ struct cdev; dev_t dev2udev(struct cdev *x); const char *devtoname(struct cdev *cdev); #ifdef __LP64__ size_t devfs_iosize_max(void); size_t iosize_max(void); #endif int poll_no_poll(int events); /* XXX: Should be void nanodelay(u_int nsec); */ void DELAY(int usec); /* Root mount holdback API */ struct root_hold_token { int flags; const char *who; TAILQ_ENTRY(root_hold_token) list; }; struct root_hold_token *root_mount_hold(const char *identifier); void root_mount_hold_token(const char *identifier, struct root_hold_token *h); void root_mount_rel(struct root_hold_token *h); int root_mounted(void); /* * Unit number allocation API. (kern/subr_unit.c) */ struct unrhdr; struct unrhdr *new_unrhdr(int low, int high, struct mtx *mutex); void init_unrhdr(struct unrhdr *uh, int low, int high, struct mtx *mutex); void delete_unrhdr(struct unrhdr *uh); void clear_unrhdr(struct unrhdr *uh); void clean_unrhdr(struct unrhdr *uh); void clean_unrhdrl(struct unrhdr *uh); int alloc_unr(struct unrhdr *uh); int alloc_unr_specific(struct unrhdr *uh, u_int item); int alloc_unrl(struct unrhdr *uh); void free_unr(struct unrhdr *uh, u_int item); #ifndef __LP64__ #define UNR64_LOCKED #endif struct unrhdr64 { uint64_t counter; }; static __inline void new_unrhdr64(struct unrhdr64 *unr64, uint64_t low) { unr64->counter = low; } #ifdef UNR64_LOCKED uint64_t alloc_unr64(struct unrhdr64 *); #else static __inline uint64_t alloc_unr64(struct unrhdr64 *unr64) { return (atomic_fetchadd_64(&unr64->counter, 1)); } #endif void intr_prof_stack_use(struct thread *td, struct trapframe *frame); void counted_warning(unsigned *counter, const char *msg); /* * APIs to manage deprecation and obsolescence. */ void _gone_in(int major, const char *msg); void _gone_in_dev(device_t dev, int major, const char *msg); #ifdef NO_OBSOLETE_CODE #define __gone_ok(m, msg) \ _Static_assert(m < P_OSREL_MAJOR(__FreeBSD_version)), \ "Obsolete code: " msg); #else #define __gone_ok(m, msg) #endif #define gone_in(major, msg) __gone_ok(major, msg) _gone_in(major, msg) #define gone_in_dev(dev, major, msg) __gone_ok(major, msg) _gone_in_dev(dev, major, msg) #if defined(INVARIANTS) || defined(WITNESS) #define __diagused #else #define __diagused __unused #endif #endif /* _KERNEL */ __NULLABILITY_PRAGMA_POP #endif /* !_SYS_SYSTM_H_ */