From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Additions/solaris/Mouse/Makefile.kmk | 81 ++ src/VBox/Additions/solaris/Mouse/deps.asm | 49 + .../Additions/solaris/Mouse/testcase/Makefile.kup | 0 .../Additions/solaris/Mouse/testcase/solaris.h | 454 ++++++ .../solaris/Mouse/testcase/tstVBoxMouse-solaris.c | 170 +++ src/VBox/Additions/solaris/Mouse/vboxms.c | 1450 ++++++++++++++++++++ src/VBox/Additions/solaris/Mouse/vboxms.conf | 42 + src/VBox/Additions/solaris/Mouse/vboxmslnk.c | 217 +++ src/VBox/Additions/solaris/Mouse/vboxmslnk.xml | 92 ++ 9 files changed, 2555 insertions(+) create mode 100644 src/VBox/Additions/solaris/Mouse/Makefile.kmk create mode 100644 src/VBox/Additions/solaris/Mouse/deps.asm create mode 100644 src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup create mode 100644 src/VBox/Additions/solaris/Mouse/testcase/solaris.h create mode 100644 src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c create mode 100644 src/VBox/Additions/solaris/Mouse/vboxms.c create mode 100644 src/VBox/Additions/solaris/Mouse/vboxms.conf create mode 100644 src/VBox/Additions/solaris/Mouse/vboxmslnk.c create mode 100644 src/VBox/Additions/solaris/Mouse/vboxmslnk.xml (limited to 'src/VBox/Additions/solaris/Mouse') diff --git a/src/VBox/Additions/solaris/Mouse/Makefile.kmk b/src/VBox/Additions/solaris/Mouse/Makefile.kmk new file mode 100644 index 00000000..b3fec956 --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/Makefile.kmk @@ -0,0 +1,81 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Solaris Mouse Integration kernel module. +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +#ifneq ($(KBUILD_HOST),solaris) +#$(error "The Solaris guest additions can only be built on Solaris!") +#endif + +# +# vboxms - The Mouse Integration Driver +# +SYSMODS.solaris += vboxms +vboxms_TEMPLATE = VBoxGuestR0Drv +vboxms_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) +vboxms_DEPS += $(VBOX_SVN_REV_KMK) +vboxms_SOURCES = \ + vboxms.c +vboxms_LIBS = \ + $(VBOX_LIB_VBGL_R0) +ifeq ($(KBUILD_HOST),solaris) + vboxms_LDFLAGS += -N drv/vboxguest -N misc/ctf +else + vboxms_SOURCES += deps.asm + vboxms_deps.asm_ASFLAGS = -f bin -g null +endif + + +PROGRAMS += vboxmslnk +vboxmslnk_TEMPLATE = VBoxGuestR3Exe +vboxmslnk_SOURCES = vboxmslnk.c + + +if 0 # Broken - unresolved externals: vbglDriver*, RTR0AssertPanicSystem. + if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + PROGRAMS += tstVBoxMouse-solaris + tstVBoxMouse-solaris_TEMPLATE = VBoxR3TstExe + tstVBoxMouse-solaris_SOURCES = \ + vboxms.c \ + testcase/tstVBoxMouse-solaris.c + tstVBoxMouse-solaris_DEFS = TESTCASE + tstVBoxMouse-solaris_LIBS = $(LIB_RUNTIME) + endif +endif + +include $(KBUILD_PATH)/subfooter.kmk + diff --git a/src/VBox/Additions/solaris/Mouse/deps.asm b/src/VBox/Additions/solaris/Mouse/deps.asm new file mode 100644 index 00000000..d01557ed --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/deps.asm @@ -0,0 +1,49 @@ +; $Id: deps.asm $ +;; @file +; Solaris kernel module dependency +; + +; +; Copyright (C) 2012-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/solaris/kmoddeps.mac" + +kmoddeps_header ; ELF header, section table and shared string table + +kmoddeps_dynstr_start ; ELF .dynstr section +kmoddeps_dynstr_string str_misc_ctf, "misc/ctf" +kmoddeps_dynstr_string str_drv_vboxguest, "drv/vboxguest" +kmoddeps_dynstr_end + +kmoddeps_dynamic_start ; ELF .dynamic section +kmoddeps_dynamic_needed str_misc_ctf +kmoddeps_dynamic_needed str_drv_vboxguest +kmoddeps_dynamic_end diff --git a/src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup b/src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/Additions/solaris/Mouse/testcase/solaris.h b/src/VBox/Additions/solaris/Mouse/testcase/solaris.h new file mode 100644 index 00000000..2040bba6 --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/testcase/solaris.h @@ -0,0 +1,454 @@ +/* $Id: solaris.h $ */ +/** @file + * VBoxGuest - Guest Additions Driver for Solaris - testcase stubs. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_Mouse_testcase_solaris_h +#define GA_INCLUDED_SRC_solaris_Mouse_testcase_solaris_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include /* RT_ZERO */ +#ifdef RT_OS_WINDOWS +# include /* struct timeval */ +#endif +#include +#include /* struct timeval */ + +/* Overrides */ +#define dev_t unsigned + +/* Constants */ +#define DDI_FAILURE (-1) +#define DDI_SUCCESS (0) + +#define MODMAXNAMELEN 32 +#define MODMAXLINKINFOLEN 32 +#define MODMAXLINK 10 + +#define MOD_NOAUTOUNLOAD 0x1 + +#define M_DATA 0x00 +#define M_BREAK 0x08 +#define M_PASSFP 0x09 +#define M_EVENT 0x0a +#define M_SIG 0x0b +#define M_DELAY 0x0c +#define M_CTL 0x0d +#define M_IOCTL 0x0e +#define M_SETOPTS 0x10 +#define M_RSE 0x11 + +#define M_IOCACK 0x81 +#define M_IOCNAK 0x82 +#define M_PCPROTO 0x83 +#define M_PCSIG 0x84 +#define M_READ 0x85 +#define M_FLUSH 0x86 +#define M_STOP 0x87 +#define M_START 0x88 +#define M_HANGUP 0x89 +#define M_ERROR 0x8a +#define M_COPYIN 0x8b +#define M_COPYOUT 0x8c +#define M_IOCDATA 0x8d +#define M_PCRSE 0x8e +#define M_STOPI 0x8f +#define M_STARTI 0x90 +#define M_PCEVENT 0x91 +#define M_UNHANGUP 0x92 +#define M_CMD 0x93 + +#define BPRI_LO 1 +#define BPRI_MED 2 +#define BPRI_HI 3 + +#define FLUSHALL 1 +#define FLUSHDATA 0 + +#define TRANSPARENT (unsigned int)(-1) + +#define FLUSHR 0x01 +#define FLUSHW 0x02 + +#define MSIOC ('m'<<8) +#define MSIOGETPARMS (MSIOC|1) +#define MSIOSETPARMS (MSIOC|2) +#define MSIOBUTTONS (MSIOC|3) +#define MSIOSRESOLUTION (MSIOC|4) + +#define VUIOC ('v'<<8) +#define VUIDSFORMAT (VUIOC|1) +#define VUIDGFORMAT (VUIOC|2) +#define VUID_NATIVE 0 +#define VUID_FIRM_EVENT 1 + +#define VUIDSADDR (VUIOC|3) +#define VUIDGADDR (VUIOC|4) + +#define VUID_WHEEL_MAX_COUNT 256 +#define VUIDGWHEELCOUNT (VUIOC|15) +#define VUIDGWHEELINFO (VUIOC|16) +#define VUIDGWHEELSTATE (VUIOC|17) +#define VUIDSWHEELSTATE (VUIOC|18) + +#define DDI_DEVICE_ATTR_V0 0x0001 +#define DDI_DEVICE_ATTR_V1 0x0002 + +#define DDI_NEVERSWAP_ACC 0x00 +#define DDI_STRUCTURE_LE_ACC 0x01 +#define DDI_STRUCTURE_BE_ACC 0x02 + +#define DDI_STRICTORDER_ACC 0x00 +#define DDI_UNORDERED_OK_ACC 0x01 +#define DDI_MERGING_OK_ACC 0x02 +#define DDI_LOADCACHING_OK_ACC 0x03 +#define DDI_STORECACHING_OK_ACC 0x04 + +/** @todo fix this */ +#define DDI_DEFAULT_ACC DDI_STRICTORDER_ACC + +#define DDI_INTR_CLAIMED 1 +#define DDI_INTR_UNCLAIMED 0 + +#define DDI_INTR_TYPE_FIXED 0x1 +#define DDI_INTR_TYPE_MSI 0x2 +#define DDI_INTR_TYPE_MSIX 0x4 + +#define LOC_FIRST_DELTA 32640 +#define LOC_X_DELTA 32640 +#define LOC_Y_DELTA 32641 +#define LOC_LAST_DELTA 32641 +#define LOC_FIRST_ABSOLUTE 32642 +#define LOC_X_ABSOLUTE 32642 +#define LOC_Y_ABSOLUTE 32643 +#define LOC_LAST_ABSOLUTE 32643 + +#define FE_PAIR_NONE 0 +#define FE_PAIR_SET 1 +#define FE_PAIR_DELTA 2 +#define FE_PAIR_ABSOLUTE 3 + +typedef struct __ldi_handle *ldi_handle_t; + +typedef enum +{ + DDI_INFO_DEVT2DEVINFO = 0, + DDI_INFO_DEVT2INSTANCE = 1 +} ddi_info_cmd_t; + +typedef enum +{ + DDI_ATTACH = 0, + DDI_RESUME = 1, + DDI_PM_RESUME = 2 +} ddi_attach_cmd_t; + +typedef enum +{ + DDI_DETACH = 0, + DDI_SUSPEND = 1, + DDI_PM_SUSPEND = 2, + DDI_HOTPLUG_DETACH = 3 +} ddi_detach_cmd_t; + +/* Simple types */ + +typedef struct cred *cred_t; +typedef struct dev_info *dev_info_t; +typedef struct __ddi_acc_handle * ddi_acc_handle_t; +typedef struct __ddi_intr_handle *ddi_intr_handle_t; +typedef struct mutex *kmutex_t; +typedef unsigned int uint_t; +typedef unsigned short ushort_t; +typedef unsigned char uchar_t; + +/* Structures */ + +struct modspecific_info { + char msi_linkinfo[MODMAXLINKINFOLEN]; + int msi_p0; +}; + +struct modinfo { + int mi_info; + int mi_state; + int mi_id; + int mi_nextid; + char *mi_base; /* Was caddr_t. */ + size_t mi_size; + int mi_rev; + int mi_loadcnt; + char mi_name[MODMAXNAMELEN]; + struct modspecific_info mi_msinfo[MODMAXLINK]; +}; + +typedef struct queue +{ + struct qinit *q_qinfo; + struct msgb *q_first; + struct msgb *q_last; + struct queue *q_next; + void *q_ptr; + size_t q_count; + uint_t q_flag; + ssize_t q_minpsz; + ssize_t q_maxpsz; + size_t q_hiwat; + size_t q_lowat; +} queue_t; + +typedef struct msgb +{ + struct msgb *b_next; + struct msgb *b_prev; + struct msgb *b_cont; + unsigned char *b_rptr; + unsigned char *b_wptr; + struct datab *b_datap; + unsigned char b_band; + unsigned short b_flag; +} mblk_t; + +typedef struct datab +{ + unsigned char *db_base; + unsigned char *db_lim; + unsigned char db_ref; + unsigned char db_type; +} dblk_t; + +struct iocblk +{ + int ioc_cmd; + cred_t *ioc_cr; + uint_t ioc_id; + uint_t ioc_flag; + size_t ioc_count; + int ioc_rval; + int ioc_error; +#if defined(RT_ARCH_AMD64) /* Actually this should be LP64. */ + int dummy; /* For simplicity, to ensure the structure size matches + struct copyreq. */ +#endif +}; + +struct copyreq +{ + int cq_cmd; + cred_t *cq_cr; + uint_t cq_id; + uint_t cq_flag; + mblk_t *cq_private; + char *cq_addr; /* Was caddr_t. */ + size_t cq_size; +}; + +struct copyresp +{ + int cp_cmd; + cred_t *cp_cr; + uint_t cp_id; + uint_t cp_flag; + mblk_t *cp_private; + char *cp_rval; /* Was caddr_t. */ +}; + +typedef struct modctl +{ + /* ... */ + char mod_loadflags; + /* ... */ +} modctl_t; + +typedef struct { + int jitter_thresh; + int speed_law; + int speed_limit; +} Ms_parms; + +typedef struct { + int height; + int width; +} Ms_screen_resolution; + +typedef struct vuid_addr_probe { + short base; + union + { + short next; + short current; + } data; +} Vuid_addr_probe; + +typedef struct ddi_device_acc_attr +{ + ushort_t devacc_attr_version; + uchar_t devacc_attr_endian_flags; + uchar_t devacc_attr_dataorder; + uchar_t devacc_attr_access; +} ddi_device_acc_attr_t; + +typedef struct firm_event +{ + ushort_t id; + uchar_t pair_type; + uchar_t pair; + int value; + struct timeval time; +} Firm_event; + +/* Prototypes */ + +#define _init vboxguestSolarisInit +extern int vboxguestSolarisInit(void); +#define _fini vboxguestSolarisFini +extern int vboxguestSolarisFini(void); +#define _info vboxguestSolarisInfo +extern int vboxguestSolarisInfo(struct modinfo *pModInfo); + +/* Simple API stubs */ + +#define cmn_err(...) do {} while(0) +#define mod_remove(...) 0 +#define mod_info(...) 0 +#define RTR0Init(...) VINF_SUCCESS +#define RTR0Term(...) do {} while(0) +#define RTR0AssertPanicSystem(...) do {} while(0) +#define RTLogCreate(...) VINF_SUCCESS +#define RTLogRelSetDefaultInstance(...) do {} while(0) +#define RTLogDestroy(...) do {} while(0) +#if 0 +#define VBoxGuestCreateKernelSession(...) VINF_SUCCESS +#define VBoxGuestCreateUserSession(...) VINF_SUCCESS +#define VBoxGuestCloseSession(...) do {} while(0) +#define VBoxGuestInitDevExt(...) VINF_SUCCESS +#define VBoxGuestDeleteDevExt(...) do {} while(0) +#define VBoxGuestCommonIOCtl(...) VINF_SUCCESS +#define VBoxGuestCommonISR(...) true +#define VbglR0GRAlloc(...) VINF_SUCCESS +#define VbglR0GRPerform(...) VINF_SUCCESS +#define VbglR0GRFree(...) do {} while(0) +#endif +#define VbglR0InitClient(...) VINF_SUCCESS +#define vbglDriverOpen(...) VINF_SUCCESS +#define vbglDriverClose(...) do {} while(0) +#define vbglDriverIOCtl(...) VINF_SUCCESS +#define qprocson(...) do {} while(0) +#define qprocsoff(...) do {} while(0) +#define flushq(...) do {} while(0) +#define putnext(...) do {} while(0) +#define ddi_get_instance(...) 0 +#define pci_config_setup(...) DDI_SUCCESS +#define pci_config_teardown(...) do {} while(0) +#define ddi_regs_map_setup(...) DDI_SUCCESS +#define ddi_regs_map_free(...) do {} while(0) +#define ddi_dev_regsize(...) DDI_SUCCESS +#define ddi_create_minor_node(...) DDI_SUCCESS +#define ddi_remove_minor_node(...) do {} while(0) +#define ddi_intr_get_supported_types(...) DDI_SUCCESS +#define ddi_intr_get_nintrs(...) DDI_SUCCESS +#define ddi_intr_get_navail(...) DDI_SUCCESS +#define ddi_intr_alloc(...) DDI_SUCCESS +#define ddi_intr_free(...) do {} while(0) +#define ddi_intr_get_pri(...) DDI_SUCCESS +#define ddi_intr_enable(...) DDI_SUCCESS +#define ddi_intr_disable(...) DDI_SUCCESS +#define ddi_intr_add_handler(...) DDI_SUCCESS +#define ddi_intr_remove_handler(...) DDI_SUCCESS +#define mutex_init(...) do {} while(0) +#define mutex_destroy(...) do {} while(0) +#define mutex_enter(...) do {} while(0) +#define mutex_exit(...) do {} while(0) +#define uniqtime32(...) do {} while(0) +#define canput(...) true +#define putbq(...) do {} while(0) + +/* Externally defined helpers. */ + +/** Flags set in the struct mblk b_flag member for verification purposes. + * @{ */ +/** miocpullup was called for this message. */ +#define F_TEST_PULLUP 1 +/** @} */ + +extern void miocack(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int rc); +extern void miocnak(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int iErr); +extern int miocpullup(mblk_t *pMBlk, size_t cbMsg); +extern void mcopyin(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser); +extern void mcopyout(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser, + mblk_t *pMBlkData); +extern void qreply(queue_t *pQueue, mblk_t *pMBlk); +extern mblk_t *allocb(size_t cb, uint_t cPrio); +extern void freemsg(mblk_t *pMsg); + +/* API stubs with simple logic */ + +static modctl_t s_ModCtl; +static void **s_pvLinkage; + +static inline modctl_t *mod_getctl(void **linkage) +{ + s_pvLinkage = linkage; + return s_pvLinkage ? &s_ModCtl : NULL; +} + +#define mod_install(linkage) (s_pvLinkage && ((linkage) == s_pvLinkage) ? 0 : EINVAL) +#define QREADR 0x00000010 +#define OTHERQ(q) ((q)->q_flag & QREADR ? (q) + 1 : (q) - 1) +#define WR(q) ((q)->q_flag & QREADR ? (q) + 1 : (q)) +#define RD(q) ((q)->q_flag & QREADR ? (q) : (q) - 1) + + +/* Basic initialisation of a queue structure pair for testing. */ +static inline void doInitQueues(queue_t aQueues[2]) +{ + aQueues[0].q_flag = QREADR; +} + +static inline dev_t makedevice(unsigned cMajor, unsigned cMinor) +{ + return cMajor * 4096 + cMinor; +} + +static inline unsigned getmajor(dev_t device) +{ + return device / 4096; +} + +/* API stubs with controllable logic */ + +#endif /* !GA_INCLUDED_SRC_solaris_Mouse_testcase_solaris_h */ diff --git a/src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c b/src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c new file mode 100644 index 00000000..f95fbd99 --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c @@ -0,0 +1,170 @@ +/** @file + * VirtualBox Guest Additions Driver for Solaris - Solaris helper functions. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#include "solaris.h" +#include + + +/********************************************************************************************************************************* +* Helper functions * +*********************************************************************************************************************************/ + +void miocack(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int rc) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + + pMBlk->b_datap->db_type = M_IOCACK; + pIOCBlk->ioc_count = cbData; + pIOCBlk->ioc_rval = rc; + pIOCBlk->ioc_error = 0; + qreply(pWriteQueue, pMBlk); +} + +void miocnak(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int iErr) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + + pMBlk->b_datap->db_type = M_IOCNAK; + pIOCBlk->ioc_count = cbData; + pIOCBlk->ioc_error = iErr ? iErr : EINVAL; + pIOCBlk->ioc_rval = 0; + qreply(pWriteQueue, pMBlk); +} + +/* This does not work like the real version, but does some sanity testing + * and sets a flag. */ +int miocpullup(mblk_t *pMBlk, size_t cbMsg) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + + if (pIOCBlk->ioc_count == TRANSPARENT) + return EINVAL; + if ( !pMBlk->b_cont + || pMBlk->b_cont->b_wptr < pMBlk->b_cont->b_rptr + cbMsg) + return EINVAL; + pMBlk->b_flag |= F_TEST_PULLUP; + return 0; +} + +void mcopyin(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + struct copyreq *pCopyReq = (struct copyreq *)pMBlk->b_rptr; + + AssertReturnVoid( pvUser + || ( pMBlk->b_datap->db_type == M_IOCTL + && pIOCBlk->ioc_count == TRANSPARENT + && pMBlk->b_cont->b_rptr)); + pMBlk->b_datap->db_type = M_COPYIN; + pMBlk->b_wptr = pMBlk->b_rptr + sizeof(*pCopyReq); + pCopyReq->cq_private = pvState; + pCopyReq->cq_size = cbData; + pCopyReq->cq_addr = pvUser ? pvUser : *(void **)pMBlk->b_cont->b_rptr; + if (pMBlk->b_cont) + { + freemsg(pMBlk->b_cont); + pMBlk->b_cont = NULL; + } +} + +void mcopyout(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser, + mblk_t *pMBlkData) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + struct copyreq *pCopyReq = (struct copyreq *)pMBlk->b_rptr; + + AssertReturnVoid( pvUser + || ( pMBlk->b_datap->db_type == M_IOCTL + && pIOCBlk->ioc_count == TRANSPARENT + && pMBlk->b_cont->b_rptr)); + pMBlk->b_datap->db_type = M_COPYOUT; + pMBlk->b_wptr = pMBlk->b_rptr + sizeof(*pCopyReq); + pCopyReq->cq_private = pvState; + pCopyReq->cq_size = cbData; + pCopyReq->cq_addr = pvUser ? pvUser : *(void **)pMBlk->b_cont->b_rptr; + if (pMBlkData) + { + if (pMBlk->b_cont) + freemsg(pMBlk->b_cont); + pMBlk->b_cont = pMBlkData; + pMBlkData->b_wptr = pMBlkData->b_rptr + cbData; + } +} + +/* This does not work like the real version but is easy to test the result of. + */ +void qreply(queue_t *pQueue, mblk_t *pMBlk) +{ + OTHERQ(pQueue)->q_first = pMBlk; +} + +/** @todo reference counting */ +mblk_t *allocb(size_t cb, uint_t cPrio) +{ + unsigned char *pch = RTMemAllocZ(cb); + struct msgb *pMBlk = (struct msgb *)RTMemAllocZ(sizeof(struct msgb)); + struct datab *pDBlk = (struct datab *)RTMemAllocZ(sizeof(struct datab)); + if (!pch || !pMBlk || !pDBlk) + { + RTMemFree(pch); + RTMemFree(pMBlk); + RTMemFree(pDBlk); + return NULL; + } + NOREF(cPrio); + pMBlk->b_rptr = pch; + pMBlk->b_wptr = pMBlk->b_rptr + cb; + pMBlk->b_datap = pDBlk; + pDBlk->db_base = pMBlk->b_rptr; + pDBlk->db_lim = pMBlk->b_wptr; + pDBlk->db_type = M_DATA; + return pMBlk; +} + +/** @todo reference counting */ +void freemsg(mblk_t *pMBlk) +{ + if (!pMBlk) + return; + RTMemFree(pMBlk->b_rptr); + RTMemFree(pMBlk->b_datap); + freemsg(pMBlk->b_cont); + RTMemFree(pMBlk); +} diff --git a/src/VBox/Additions/solaris/Mouse/vboxms.c b/src/VBox/Additions/solaris/Mouse/vboxms.c new file mode 100644 index 00000000..173db83e --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/vboxms.c @@ -0,0 +1,1450 @@ +/* $Id: vboxms.c $ */ +/** @file + * VirtualBox Guest Additions Mouse Driver for Solaris. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_MOUSE +#include +#include +#include +#include +#include +#include + +#ifndef TESTCASE +# include +# include +# include +# include +# include +# include +# include +# include +# include +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ +#else /* TESTCASE */ +# undef IN_RING3 +# define IN_RING0 +#endif /* TESTCASE */ + +#ifdef TESTCASE /* Include this last as we . */ +# include "testcase/solaris.h" +# include +#endif /* TESTCASE */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** The module name. */ +#define DEVICE_NAME "vboxms" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC "VBoxMouseIntegr" + + +/********************************************************************************************************************************* +* Internal functions used in global structures * +*********************************************************************************************************************************/ + +static int vbmsSolAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +static int vbmsSolDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +static int vbmsSolGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, + void **ppvResult); +static int vbmsSolOpen(queue_t *pReadQueue, dev_t *pDev, int fFlag, + int fMode, cred_t *pCred); +static int vbmsSolClose(queue_t *pReadQueue, int fFlag, cred_t *pCred); +static int vbmsSolWPut(queue_t *pWriteQueue, mblk_t *pMBlk); + + +/********************************************************************************************************************************* +* Driver global structures * +*********************************************************************************************************************************/ + +#ifndef TESTCASE /* I see no value in including these in the test. */ + +/* + * mod_info: STREAMS module information. + */ +static struct module_info g_vbmsSolModInfo = +{ + 0, /* module id number */ + "vboxms", + 0, /* minimum packet size */ + INFPSZ, /* maximum packet size accepted */ + 512, /* high water mark for data flow control */ + 128 /* low water mark */ +}; + +/* + * rinit: read queue structure for handling messages coming from below. In + * our case this means the host and the virtual hardware, so we do not need + * the put and service procedures. + */ +static struct qinit g_vbmsSolRInit = +{ + NULL, /* put */ + NULL, /* service thread procedure */ + vbmsSolOpen, + vbmsSolClose, + NULL, /* reserved */ + &g_vbmsSolModInfo, + NULL /* module statistics structure */ +}; + +/* + * winit: write queue structure for handling messages coming from above. Above + * means user space applications: either Guest Additions user space tools or + * applications reading pointer input. Messages from the last most likely pass + * through at least the "consms" console mouse streams module which multiplexes + * hardware pointer drivers to a single virtual pointer. + */ +static struct qinit g_vbmsSolWInit = +{ + vbmsSolWPut, + NULL, /* service thread procedure */ + NULL, /* open */ + NULL, /* close */ + NULL, /* reserved */ + &g_vbmsSolModInfo, + NULL /* module statistics structure */ +}; + +/** + * streamtab: for drivers that support char/block entry points. + */ +static struct streamtab g_vbmsSolStreamTab = +{ + &g_vbmsSolRInit, + &g_vbmsSolWInit, + NULL, /* MUX rinit */ + NULL /* MUX winit */ +}; + +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_vbmsSolCbOps = +{ + nodev, /* open */ + nodev, /* close */ + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + nodev, /* c read */ + nodev, /* c write */ + nodev, /* c ioctl */ + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + &g_vbmsSolStreamTab, + D_MP, + CB_REV /* revision */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_vbmsSolDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + vbmsSolGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + vbmsSolAttach, + vbmsSolDetach, + nodev, /* reset */ + &g_vbmsSolCbOps, + NULL, /* bus operations */ + nodev /* power */ +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_vbmsSolModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_vbmsSolDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel. + */ +static struct modlinkage g_vbmsSolModLinkage = +{ + MODREV_1, /* loadable module system revision */ + &g_vbmsSolModule, + NULL /* terminate array of linkage structures */ +}; + +#else /* TESTCASE */ +static void *g_vbmsSolModLinkage; +#endif /* TESTCASE */ + +/** + * State info for each open file handle. + */ +typedef struct +{ + /** Device handle. */ + dev_info_t *pDip; + /** Mutex protecting the guest library against multiple initialistation or + * uninitialisation. */ + kmutex_t InitMtx; + /** Initialisation counter for the guest library. */ + size_t cInits; + /** The STREAMS write queue which we need for sending messages up to + * user-space. */ + queue_t *pWriteQueue; + /** Pre-allocated mouse status VMMDev request for use in the IRQ + * handler. */ + VMMDevReqMouseStatus *pMouseStatusReq; + /* The current greatest horizontal pixel offset on the screen, used for + * absolute mouse position reporting. + */ + int cMaxScreenX; + /* The current greatest vertical pixel offset on the screen, used for + * absolute mouse position reporting. + */ + int cMaxScreenY; +} VBMSSTATE, *PVBMSSTATE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Global driver state. Actually this could be allocated dynamically. */ +static VBMSSTATE g_OpenNodeState /* = { 0 } */; + + +/********************************************************************************************************************************* +* Kernel entry points * +*********************************************************************************************************************************/ + +/** Driver initialisation. */ +int _init(void) +{ + int rc; + LogRelFlow((DEVICE_NAME ": built on " __DATE__ " at " __TIME__ "\n")); + mutex_init(&g_OpenNodeState.InitMtx, NULL, MUTEX_DRIVER, NULL); + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_vbmsSolModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ": failed to disable autounloading!\n")); + rc = mod_install(&g_vbmsSolModLinkage); + + LogRelFlow((DEVICE_NAME ": initialisation returning %d.\n", rc)); + return rc; +} + + +#ifdef TESTCASE +/** Simple test of the flow through _init. */ +static void test_init(RTTEST hTest) +{ + RTTestSub(hTest, "Testing _init"); + RTTEST_CHECK(hTest, _init() == 0); +} +#endif + + +/** Driver cleanup. */ +int _fini(void) +{ + int rc; + + LogRelFlow((DEVICE_NAME ":_fini\n")); + rc = mod_remove(&g_vbmsSolModLinkage); + if (!rc) + mutex_destroy(&g_OpenNodeState.InitMtx); + + return rc; +} + + +/** Driver identification. */ +int _info(struct modinfo *pModInfo) +{ + int rc; + LogRelFlow((DEVICE_NAME ":_info\n")); + rc = mod_info(&g_vbmsSolModLinkage, pModInfo); + LogRelFlow((DEVICE_NAME ":_info returning %d\n", rc)); + return rc; +} + + +/********************************************************************************************************************************* +* Initialisation entry points * +*********************************************************************************************************************************/ + +/** + * Attach entry point, to attach a device to the system or resume it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @return corresponding solaris error code. + */ +int vbmsSolAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogRelFlow((DEVICE_NAME "::Attach\n")); + switch (enmCmd) + { + case DDI_ATTACH: + { + int rc; + /* Only one instance supported. */ + if (!ASMAtomicCmpXchgPtr(&g_OpenNodeState.pDip, pDip, NULL)) + return DDI_FAILURE; + rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, 0 /* flags */); + if (rc == DDI_SUCCESS) + return DDI_SUCCESS; + ASMAtomicWritePtr(&g_OpenNodeState.pDip, NULL); + return DDI_FAILURE; + } + + case DDI_RESUME: + { + /** @todo implement resume for guest driver. */ + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Detach entry point, to detach a device to the system or suspend it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @return corresponding solaris error code. + */ +int vbmsSolDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogRelFlow((DEVICE_NAME "::Detach\n")); + switch (enmCmd) + { + case DDI_DETACH: + { + ddi_remove_minor_node(pDip, NULL); + ASMAtomicWritePtr(&g_OpenNodeState.pDip, NULL); + return DDI_SUCCESS; + } + + case DDI_SUSPEND: + { + /** @todo implement suspend for guest driver. */ + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Info entry point, called by solaris kernel for obtaining driver info. + * + * @param pDip The module structure instance (do not use). + * @param enmCmd Information request type. + * @param pvArg Type specific argument. + * @param ppvResult Where to store the requested info. + * + * @return corresponding solaris error code. + */ +int vbmsSolGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, + void **ppvResult) +{ + LogRelFlow((DEVICE_NAME "::GetInfo\n")); + + int rc = DDI_SUCCESS; + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + *ppvResult = (void *)g_OpenNodeState.pDip; + if (!*ppvResult) + rc = DDI_FAILURE; + break; + } + + case DDI_INFO_DEVT2INSTANCE: + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; + break; + } + + default: + rc = DDI_FAILURE; + break; + } + + NOREF(pvArg); + return rc; +} + + +/********************************************************************************************************************************* +* Main code * +*********************************************************************************************************************************/ + +static void vbmsSolNotify(void *pvState); +static void vbmsSolVUIDPutAbsEvent(PVBMSSTATE pState, ushort_t cEvent, + int cValue); + +/** + * Open callback for the read queue, which we use as a generic device open + * handler. + */ +int vbmsSolOpen(queue_t *pReadQueue, dev_t *pDev, int fFlag, int fMode, + cred_t *pCred) +{ + PVBMSSTATE pState = NULL; + int rc = VINF_SUCCESS; + + NOREF(fFlag); + NOREF(pCred); + LogRelFlow((DEVICE_NAME "::Open, pWriteQueue=%p\n", WR(pReadQueue))); + + /* + * Sanity check on the mode parameter - only open as a driver, not a + * module, and we do cloning ourselves. + */ + if (fMode) + { + LogRel(("::Open: invalid attempt to clone device.")); + return EINVAL; + } + + pState = &g_OpenNodeState; + mutex_enter(&pState->InitMtx); + /* + * Check and remember our STREAM queue. + */ + if ( pState->pWriteQueue + && (pState->pWriteQueue != WR(pReadQueue))) + { + mutex_exit(&pState->InitMtx); + LogRel((DEVICE_NAME "::Open: unexpectedly called with a different queue to previous calls. Exiting.\n")); + return EINVAL; + } + if (!pState->cInits) + { + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 + * init, and create a new session. + */ + rc = VbglR0InitClient(); + if (RT_SUCCESS(rc)) + { + rc = VbglR0GRAlloc((VMMDevRequestHeader **) + &pState->pMouseStatusReq, + sizeof(*pState->pMouseStatusReq), + VMMDevReq_GetMouseStatus); + if (RT_FAILURE(rc)) + VbglR0TerminateClient(); + else + { + int rc2; + /* Initialise user data for the queues to our state and + * vice-versa. */ + pState->pWriteQueue = WR(pReadQueue); + WR(pReadQueue)->q_ptr = (char *)pState; + pReadQueue->q_ptr = (char *)pState; + qprocson(pReadQueue); + /* Enable our IRQ handler. */ + rc2 = VbglR0SetMouseNotifyCallback(vbmsSolNotify, (void *)pState); + if (RT_FAILURE(rc2)) + /* Log the failure. I may well have not understood what + * is going on here, and the logging may help me. */ + LogRelFlow(("Failed to install the event handler call-back, rc=%Rrc\n", + rc2)); + } + } + } + if (RT_SUCCESS(rc)) + ++pState->cInits; + mutex_exit(&pState->InitMtx); + if (RT_FAILURE(rc)) + { + LogRel(("open time initialisation failed. rc=%d\n", rc)); + ASMAtomicWriteNullPtr(&pState->pWriteQueue); + return EINVAL; + } + return 0; +} + + +/** + * Notification callback, called when the VBoxGuest mouse pointer is moved. + * We send a VUID event up to user space. We may send a miscalculated event + * if a resolution change is half-way through, but that is pretty much to be + * expected, so we won't worry about it. + */ +void vbmsSolNotify(void *pvState) +{ + PVBMSSTATE pState = (PVBMSSTATE)pvState; + int rc; + + pState->pMouseStatusReq->mouseFeatures = 0; + pState->pMouseStatusReq->pointerXPos = 0; + pState->pMouseStatusReq->pointerYPos = 0; + rc = VbglR0GRPerform(&pState->pMouseStatusReq->header); + if (RT_SUCCESS(rc)) + { + int cMaxScreenX = pState->cMaxScreenX; + int cMaxScreenY = pState->cMaxScreenY; + int x = pState->pMouseStatusReq->pointerXPos; + int y = pState->pMouseStatusReq->pointerYPos; + + if (cMaxScreenX && cMaxScreenY) + { + vbmsSolVUIDPutAbsEvent(pState, LOC_X_ABSOLUTE, + x * cMaxScreenX / VMMDEV_MOUSE_RANGE_MAX); + vbmsSolVUIDPutAbsEvent(pState, LOC_Y_ABSOLUTE, + y * cMaxScreenY / VMMDEV_MOUSE_RANGE_MAX); + } + } +} + + +void vbmsSolVUIDPutAbsEvent(PVBMSSTATE pState, ushort_t cEvent, + int cValue) +{ + queue_t *pReadQueue = RD(pState->pWriteQueue); + mblk_t *pMBlk = allocb(sizeof(Firm_event), BPRI_HI); + Firm_event *pEvent; + AssertReturnVoid(cEvent == LOC_X_ABSOLUTE || cEvent == LOC_Y_ABSOLUTE); + if (!pMBlk) + return; /* If kernel memory is short a missed event is acceptable! */ + pEvent = (Firm_event *)pMBlk->b_wptr; + pEvent->id = cEvent; + pEvent->pair_type = FE_PAIR_DELTA; + pEvent->pair = cEvent == LOC_X_ABSOLUTE ? LOC_X_DELTA : LOC_Y_DELTA; + pEvent->value = cValue; + uniqtime32(&pEvent->time); + pMBlk->b_wptr += sizeof(Firm_event); + /* Put the message on the queue immediately if it is not blocked. */ + if (canput(pReadQueue->q_next)) + putnext(pReadQueue, pMBlk); + else + putq(pReadQueue, pMBlk); +} + + +/** + * Close callback for the read queue, which we use as a generic device close + * handler. + */ +int vbmsSolClose(queue_t *pReadQueue, int fFlag, cred_t *pCred) +{ + PVBMSSTATE pState = (PVBMSSTATE)pReadQueue->q_ptr; + + LogRelFlow((DEVICE_NAME "::Close, pWriteQueue=%p\n", WR(pReadQueue))); + NOREF(fFlag); + NOREF(pCred); + + if (!pState) + { + Log((DEVICE_NAME "::Close: failed to get pState.\n")); + return EFAULT; + } + + mutex_enter(&pState->InitMtx); + --pState->cInits; + if (!pState->cInits) + { + VbglR0SetMouseStatus(0); + /* Disable our IRQ handler. */ + VbglR0SetMouseNotifyCallback(NULL, NULL); + qprocsoff(pReadQueue); + + /* + * Close the session. + */ + ASMAtomicWriteNullPtr(&pState->pWriteQueue); + pReadQueue->q_ptr = NULL; + VbglR0GRFree(&pState->pMouseStatusReq->header); + VbglR0TerminateClient(); + } + mutex_exit(&pState->InitMtx); + return 0; +} + + +#ifdef TESTCASE +/** Simple test of vbmsSolOpen and vbmsSolClose. */ +static void testOpenClose(RTTEST hTest) +{ + queue_t aQueues[2]; + dev_t device = 0; + int rc; + + RTTestSub(hTest, "Testing vbmsSolOpen and vbmsSolClose"); + RT_ZERO(g_OpenNodeState); + RT_ZERO(aQueues); + doInitQueues(&aQueues[0]); + rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL); + RTTEST_CHECK(hTest, rc == 0); + RTTEST_CHECK(hTest, g_OpenNodeState.pWriteQueue == WR(&aQueues[0])); + vbmsSolClose(RD(&aQueues[0]), 0, NULL); +} +#endif + + +/* Helper for vbmsSolWPut. */ +static int vbmsSolDispatchIOCtl(PVBMSSTATE pState, mblk_t *pMBlk); + +/** + * Handler for messages sent from above (user-space and upper modules) which + * land in our write queue. + */ +int vbmsSolWPut(queue_t *pWriteQueue, mblk_t *pMBlk) +{ + PVBMSSTATE pState = (PVBMSSTATE)pWriteQueue->q_ptr; + LogRelFlowFunc((DEVICE_NAME "::")); + switch (pMBlk->b_datap->db_type) + { + case M_FLUSH: + LogRelFlow(("M_FLUSH, FLUSHW=%RTbool, FLUSHR=%RTbool\n", + *pMBlk->b_rptr & FLUSHW, *pMBlk->b_rptr & FLUSHR)); + /* Flush the write queue if so requested. */ + if (*pMBlk->b_rptr & FLUSHW) + flushq(pWriteQueue, FLUSHDATA); + + /* Flush the read queue if so requested. */ + if (*pMBlk->b_rptr & FLUSHR) + flushq(RD(pWriteQueue), FLUSHDATA); + + /* We have no one below us to pass the message on to. */ + freemsg(pMBlk); + return 0; + /* M_IOCDATA is additional data attached to (at least) transparent + * IOCtls. We handle the two together here and separate them further + * down. */ + case M_IOCTL: + case M_IOCDATA: + { + int err; + + LogRelFlow(( pMBlk->b_datap->db_type == M_IOCTL + ? "M_IOCTL\n" : "M_IOCDATA\n")); + err = vbmsSolDispatchIOCtl(pState, pMBlk); + if (!err) + qreply(pWriteQueue, pMBlk); + else + miocnak(pWriteQueue, pMBlk, 0, err); + break; + } + default: + LogRelFlow(("Unknown command, not acknowledging.\n")); + } + return 0; +} + + +#ifdef TESTCASE +/* Constants, definitions and test functions for testWPut. */ +static const int g_ccTestWPutFirmEvent = VUID_FIRM_EVENT; +# define PVGFORMAT (&g_ccTestWPutFirmEvent) +# define CBGFORMAT (sizeof(g_ccTestWPutFirmEvent)) +static const Ms_screen_resolution g_TestResolution = { 640, 480 }; +# define PMSIOSRES (&g_TestResolution) +# define CBMSIOSRES (sizeof(g_TestResolution)) + +static inline bool testSetResolution(RTTEST hTest, queue_t *pWriteQueue, + struct msgb *pMBlk) +{ + PVBMSSTATE pState = (PVBMSSTATE)pWriteQueue->q_ptr; + RTTEST_CHECK_MSG_RET(hTest, pState->cMaxScreenX + == g_TestResolution.width - 1, + (hTest, "pState->cMaxScreenX=%d\n", + pState->cMaxScreenX), false); + RTTEST_CHECK_MSG_RET(hTest, pState->cMaxScreenY + == g_TestResolution.height - 1, + (hTest, "pState->cMaxScreenY=%d\n", + pState->cMaxScreenY), false); + return true; +} + +/** Data table for testWPut. */ +static struct +{ + int iIOCCmd; + size_t cbData; + const void *pvDataIn; + size_t cbDataIn; + const void *pvDataOut; + size_t cbDataOut; + int rcExp; + bool (*pfnExtra)(RTTEST hTest, queue_t *pWriteQueue, struct msgb *pMBlk); + bool fCanTransparent; +} g_asTestWPut[] = +{ + /* iIOCCmd cbData pvDataIn cbDataIn + pvDataOut cbDataOut rcExp pfnExtra fCanTransparent */ + { VUIDGFORMAT, sizeof(int), NULL, 0, + PVGFORMAT, CBGFORMAT, 0, NULL, true }, + { VUIDGFORMAT, sizeof(int) - 1, NULL, 0, + NULL, 0, EINVAL, NULL, false }, + { VUIDGFORMAT, sizeof(int) + 1, NULL, 0, + PVGFORMAT, CBGFORMAT, 0, NULL, true }, + { VUIDSFORMAT, sizeof(int), PVGFORMAT, CBGFORMAT, + NULL, 0, 0, NULL, true }, + { MSIOSRESOLUTION, CBMSIOSRES, PMSIOSRES, CBMSIOSRES, + NULL, 0, 0, testSetResolution, true }, + { VUIDGWHEELINFO, 0, NULL, 0, + NULL, 0, EINVAL, NULL, true } +}; + +# undef PVGFORMAT +# undef CBGFORMAT +# undef PMSIOSRES +# undef CBMSIOSRES + +/* Helpers for testWPut. */ +static void testWPutStreams(RTTEST hTest, unsigned i); +static void testWPutTransparent(RTTEST hTest, unsigned i); +static void testWPutIOCDataIn(RTTEST hTest, unsigned i); +static void testWPutIOCDataOut(RTTEST hTest, unsigned i); + +/** Test WPut's handling of different IOCtls, which is bulk of the logic in + * this file. */ +static void testWPut(RTTEST hTest) +{ + unsigned i; + + RTTestSub(hTest, "Testing vbmsWPut"); + for (i = 0; i < RT_ELEMENTS(g_asTestWPut); ++i) + { + AssertReturnVoid(g_asTestWPut[i].cbDataIn <= g_asTestWPut[i].cbData); + AssertReturnVoid(g_asTestWPut[i].cbDataOut <= g_asTestWPut[i].cbData); + testWPutStreams(hTest, i); + if (g_asTestWPut[i].fCanTransparent) + testWPutTransparent(hTest, i); + if (g_asTestWPut[i].fCanTransparent && g_asTestWPut[i].cbDataIn) + testWPutIOCDataIn(hTest, i); + if (g_asTestWPut[i].fCanTransparent && g_asTestWPut[i].cbDataOut) + testWPutIOCDataOut(hTest, i); + } +} + + +#define MSG_DATA_SIZE 1024 + +/** Simulate sending a streams IOCtl to WPut with the parameters from table + * line @a i. */ +void testWPutStreams(RTTEST hTest, unsigned i) +{ + queue_t aQueues[2]; + dev_t device = 0; + struct msgb *pMBlk = allocb(sizeof(struct iocblk), BPRI_MED); + struct msgb *pMBlkCont = allocb(MSG_DATA_SIZE, BPRI_MED); + struct iocblk *pIOCBlk = pMBlk ? (struct iocblk *)pMBlk->b_rptr : NULL; + int rc, cFormat = 0; + + AssertReturnVoid(pMBlk); + AssertReturnVoidStmt(pMBlkCont, freemsg(pMBlk)); + RT_ZERO(aQueues); + doInitQueues(&aQueues[0]); + rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL); + RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc)); + RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue + == WR(&aQueues[0]), (hTest, "i=%u\n", i)); + pMBlk->b_datap->db_type = M_IOCTL; + pIOCBlk->ioc_cmd = g_asTestWPut[i].iIOCCmd; + pIOCBlk->ioc_count = g_asTestWPut[i].cbData; + AssertReturnVoid(g_asTestWPut[i].cbData <= MSG_DATA_SIZE); + memcpy(pMBlkCont->b_rptr, g_asTestWPut[i].pvDataIn, + g_asTestWPut[i].cbDataIn); + pMBlk->b_cont = pMBlkCont; + rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk); + RTTEST_CHECK_MSG(hTest, pIOCBlk->ioc_error == g_asTestWPut[i].rcExp, + (hTest, "i=%u, IOCBlk.ioc_error=%d\n", i, + pIOCBlk->ioc_error)); + RTTEST_CHECK_MSG(hTest, pIOCBlk->ioc_count == g_asTestWPut[i].cbDataOut, + (hTest, "i=%u, ioc_count=%u\n", i, pIOCBlk->ioc_count)); + RTTEST_CHECK_MSG(hTest, !memcmp(pMBlkCont->b_rptr, + g_asTestWPut[i].pvDataOut, + g_asTestWPut[i].cbDataOut), + (hTest, "i=%u\n", i)); + /* Hack to ensure that miocpullup() gets called when needed. */ + if (g_asTestWPut[i].cbData > 0) + RTTEST_CHECK_MSG(hTest, pMBlk->b_flag == 1, (hTest, "i=%u\n", i)); + if (!g_asTestWPut[i].rcExp) + RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk, + (hTest, "i=%u\n", i)); + if (g_asTestWPut[i].pfnExtra) + if (!g_asTestWPut[i].pfnExtra(hTest, WR(&aQueues[0]), pMBlk)) + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Called from %s.\n", + __PRETTY_FUNCTION__); + vbmsSolClose(RD(&aQueues[1]), 0, NULL); + freemsg(pMBlk); +} + + +#define USER_ADDRESS 0xfeedbacc + +/** Simulate sending a transparent IOCtl to WPut with the parameters from table + * line @a i. */ +void testWPutTransparent(RTTEST hTest, unsigned i) +{ + queue_t aQueues[2]; + dev_t device = 0; + struct msgb *pMBlk = allocb(sizeof(struct iocblk), BPRI_MED); + struct msgb *pMBlkCont = allocb(sizeof(void *), BPRI_MED); + struct iocblk *pIOCBlk = pMBlk ? (struct iocblk *)pMBlk->b_rptr : NULL; + struct copyreq *pCopyReq; + int rc, cFormat = 0; + + /* if (g_asTestWPut[i].cbDataIn == 0 && g_asTestWPut[i].cbDataOut != 0) + return; */ /* This case will be handled once the current ones work. */ + AssertReturnVoid(pMBlk); + AssertReturnVoidStmt(pMBlkCont, freemsg(pMBlk)); + RT_ZERO(aQueues); + doInitQueues(&aQueues[0]); + rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL); + RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc)); + RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue + == WR(&aQueues[0]), (hTest, "i=%u\n", i)); + pMBlk->b_datap->db_type = M_IOCTL; + pIOCBlk->ioc_cmd = g_asTestWPut[i].iIOCCmd; + pIOCBlk->ioc_count = TRANSPARENT; + *(void **)pMBlkCont->b_rptr = (void *)USER_ADDRESS; + pMBlk->b_cont = pMBlkCont; + rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk); + pCopyReq = (struct copyreq *)pMBlk->b_rptr; + RTTEST_CHECK_MSG(hTest, ( ( g_asTestWPut[i].cbDataIn + && (pMBlk->b_datap->db_type == M_COPYIN)) + || ( g_asTestWPut[i].cbDataOut + && (pMBlk->b_datap->db_type == M_COPYOUT)) + || ( (g_asTestWPut[i].rcExp == 0) + && pMBlk->b_datap->db_type == M_IOCACK) + || (pMBlk->b_datap->db_type == M_IOCNAK)), + (hTest, "i=%u, db_type=%u\n", i, + (unsigned) pMBlk->b_datap->db_type)); + /* Our TRANSPARENT IOCtls can only return non-zero if they have no payload. + * Others should either return zero or be non-TRANSPARENT only. */ + if (pMBlk->b_datap->db_type == M_IOCNAK) + RTTEST_CHECK_MSG(hTest, pIOCBlk->ioc_error == g_asTestWPut[i].rcExp, + (hTest, "i=%u, IOCBlk.ioc_error=%d\n", i, + pIOCBlk->ioc_error)); + if (g_asTestWPut[i].cbData) + { + RTTEST_CHECK_MSG(hTest, pCopyReq->cq_addr == (char *)USER_ADDRESS, + (hTest, "i=%u, cq_addr=%p\n", i, pCopyReq->cq_addr)); + RTTEST_CHECK_MSG( hTest, pCopyReq->cq_size + == g_asTestWPut[i].cbDataIn + ? g_asTestWPut[i].cbDataIn + : g_asTestWPut[i].cbDataOut, + (hTest, "i=%u, cq_size=%llu\n", i, + (unsigned long long)pCopyReq->cq_size)); + } + /* Implementation detail - check that the private pointer is correctly + * set to the user address *for two direction IOCtls* or NULL otherwise. */ + if (g_asTestWPut[i].cbDataIn && g_asTestWPut[i].cbDataOut) + RTTEST_CHECK_MSG(hTest, pCopyReq->cq_private == (mblk_t *)USER_ADDRESS, + (hTest, "i=%u, cq_private=%p\n", i, + pCopyReq->cq_private)); + else if ( (pMBlk->b_datap->db_type == M_COPYIN) + || (pMBlk->b_datap->db_type == M_COPYOUT)) + RTTEST_CHECK_MSG(hTest, !pCopyReq->cq_private, + (hTest, "i=%u, cq_private=%p\n", i, + pCopyReq->cq_private)); + if (!g_asTestWPut[i].rcExp) + RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk, + (hTest, "i=%u\n", i)); + if (g_asTestWPut[i].pfnExtra && !g_asTestWPut[i].cbData) + if (!g_asTestWPut[i].pfnExtra(hTest, WR(&aQueues[0]), pMBlk)) + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Called from %s.\n", + __PRETTY_FUNCTION__); + vbmsSolClose(RD(&aQueues[1]), 0, NULL); + freemsg(pMBlk); +} + + +/** Simulate sending follow-on IOCData messages to a transparent IOCtl to WPut + * with the parameters from table line @a i. */ +void testWPutIOCDataIn(RTTEST hTest, unsigned i) +{ + queue_t aQueues[2]; + dev_t device = 0; + struct msgb *pMBlk = allocb(sizeof(struct copyresp), BPRI_MED); + struct msgb *pMBlkCont = allocb(MSG_DATA_SIZE, BPRI_MED); + struct copyresp *pCopyResp = pMBlk ? (struct copyresp *)pMBlk->b_rptr + : NULL; + void *pvData = pMBlkCont ? pMBlkCont->b_rptr : NULL; + struct copyreq *pCopyReq; + int rc, cFormat = 0; + + AssertReturnVoid(pMBlk); + AssertReturnVoidStmt(pMBlkCont, freemsg(pMBlk)); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: i=%u\n", __PRETTY_FUNCTION__, + i); + AssertReturnVoidStmt(g_asTestWPut[i].cbDataIn, freemsg(pMBlk)); + RT_ZERO(aQueues); + doInitQueues(&aQueues[0]); + rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL); + RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc)); + RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue + == WR(&aQueues[0]), (hTest, "i=%u\n", i)); + pMBlk->b_datap->db_type = M_IOCDATA; + pCopyResp->cp_cmd = g_asTestWPut[i].iIOCCmd; + if (g_asTestWPut[i].cbDataOut) + pCopyResp->cp_private = USER_ADDRESS; + AssertReturnVoid(g_asTestWPut[i].cbData <= MSG_DATA_SIZE); + memcpy(pMBlkCont->b_rptr, g_asTestWPut[i].pvDataIn, g_asTestWPut[i].cbDataIn); + pMBlk->b_cont = pMBlkCont; + rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk); + pCopyReq = (struct copyreq *)pMBlk->b_rptr; + RTTEST_CHECK_MSG(hTest, ( ( g_asTestWPut[i].cbDataOut + && (pMBlk->b_datap->db_type == M_COPYOUT)) + || ( (g_asTestWPut[i].rcExp == 0) + && pMBlk->b_datap->db_type == M_IOCACK) + || (pMBlk->b_datap->db_type == M_IOCNAK)), + (hTest, "i=%u, db_type=%u\n", i, + (unsigned) pMBlk->b_datap->db_type)); + if (g_asTestWPut[i].cbDataOut) + { + RTTEST_CHECK_MSG(hTest, pCopyReq->cq_addr == (char *)pvData, + (hTest, "i=%u, cq_addr=%p\n", i, pCopyReq->cq_addr)); + RTTEST_CHECK_MSG(hTest, pCopyReq->cq_size == g_asTestWPut[i].cbData, + (hTest, "i=%u, cq_size=%llu\n", i, + (unsigned long long)pCopyReq->cq_size)); + RTTEST_CHECK_MSG(hTest, !memcmp(pvData, g_asTestWPut[i].pvDataOut, + g_asTestWPut[i].cbDataOut), + (hTest, "i=%u\n", i)); + } + RTTEST_CHECK_MSG(hTest, !pCopyReq->cq_private, + (hTest, "i=%u, cq_private=%p\n", i, + pCopyReq->cq_private)); + if (!g_asTestWPut[i].rcExp) + RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk, + (hTest, "i=%u\n", i)); + if (g_asTestWPut[i].pfnExtra && !g_asTestWPut[i].cbDataOut) + if (!g_asTestWPut[i].pfnExtra(hTest, WR(&aQueues[0]), pMBlk)) + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Called from %s.\n", + __PRETTY_FUNCTION__); + vbmsSolClose(RD(&aQueues[1]), 0, NULL); + freemsg(pMBlk); +} + + +/** Simulate sending follow-on IOCData messages to a transparent IOCtl to WPut + * with the parameters from table line @a i. */ +void testWPutIOCDataOut(RTTEST hTest, unsigned i) +{ + queue_t aQueues[2]; + dev_t device = 0; + struct msgb *pMBlk = allocb(sizeof(struct copyresp), BPRI_MED); + struct copyresp *pCopyResp = pMBlk ? (struct copyresp *)pMBlk->b_rptr + : NULL; + int rc, cFormat = 0; + + AssertReturnVoid(pMBlk); + AssertReturnVoidStmt(g_asTestWPut[i].cbDataOut, freemsg(pMBlk)); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: i=%u\n", __PRETTY_FUNCTION__, + i); + RT_ZERO(aQueues); + doInitQueues(&aQueues[0]); + rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL); + RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc)); + RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue + == WR(&aQueues[0]), (hTest, "i=%u\n", i)); + pMBlk->b_datap->db_type = M_IOCDATA; + pCopyResp->cp_cmd = g_asTestWPut[i].iIOCCmd; + rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk); + RTTEST_CHECK_MSG(hTest, pMBlk->b_datap->db_type == M_IOCACK, + (hTest, "i=%u, db_type=%u\n", i, + (unsigned) pMBlk->b_datap->db_type)); + if (!g_asTestWPut[i].rcExp) + RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk, + (hTest, "i=%u\n", i)); + vbmsSolClose(RD(&aQueues[1]), 0, NULL); + freemsg(pMBlk); +} +#endif + + +/** Data transfer direction of an IOCtl. This is used for describing + * transparent IOCtls, and @a UNSPECIFIED is not a valid value for them. */ +enum IOCTLDIRECTION +{ + /** This IOCtl transfers no data. */ + NONE, + /** This IOCtl only transfers data from user to kernel. */ + IN, + /** This IOCtl only transfers data from kernel to user. */ + OUT, + /** This IOCtl transfers data from user to kernel and back. */ + BOTH, + /** We aren't saying anything about how the IOCtl transfers data. */ + UNSPECIFIED +}; + +/** + * IOCtl handler function. + * @returns 0 on success, error code on failure. + * @param iCmd The IOCtl command number. + * @param pvData Buffer for the user data. + * @param cbBuffer Size of the buffer in @a pvData or zero. + * @param pcbData Where to set the size of the data returned. Required for + * handlers which return data. + * @param prc Where to store the return code. Default is zero. Only + * used for IOCtls without data for convenience of + * implemention. + */ +typedef int FNVBMSSOLIOCTL(PVBMSSTATE pState, int iCmd, void *pvData, + size_t cbBuffer, size_t *pcbData, int *prc); +typedef FNVBMSSOLIOCTL *PFNVBMSSOLIOCTL; + +/* Helpers for vbmsSolDispatchIOCtl. */ +static int vbmsSolHandleIOCtl(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, + int iCmd, size_t cbCmd, + enum IOCTLDIRECTION enmDirection); +static int vbmsSolVUIDIOCtl(PVBMSSTATE pState, int iCmd, void *pvData, + size_t cbBuffer, size_t *pcbData, int *prc); + +/** Table of supported VUID IOCtls. */ +struct +{ + /** The IOCtl number. */ + int iCmd; + /** The size of the buffer which needs to be copied between user and kernel + * space, or zero if unknown (must be known for tranparent IOCtls). */ + size_t cbBuffer; + /** The direction the buffer data needs to be copied. This must be + * specified for transparent IOCtls. */ + enum IOCTLDIRECTION enmDirection; +} g_aVUIDIOCtlDescriptions[] = +{ + { VUIDGFORMAT, sizeof(int), OUT }, + { VUIDSFORMAT, sizeof(int), IN }, + { VUIDGADDR, 0, UNSPECIFIED }, + { VUIDGADDR, 0, UNSPECIFIED }, + { MSIOGETPARMS, sizeof(Ms_parms), OUT }, + { MSIOSETPARMS, sizeof(Ms_parms), IN }, + { MSIOSRESOLUTION, sizeof(Ms_screen_resolution), IN }, + { MSIOBUTTONS, sizeof(int), OUT }, + { VUIDGWHEELCOUNT, sizeof(int), OUT }, + { VUIDGWHEELINFO, 0, UNSPECIFIED }, + { VUIDGWHEELSTATE, 0, UNSPECIFIED }, + { VUIDSWHEELSTATE, 0, UNSPECIFIED } +}; + +/** + * Handle a STREAMS IOCtl message for our driver on the write stream. This + * function takes care of the IOCtl logic only and does not call qreply() or + * miocnak() at all - the caller must call these on success or failure + * respectively. + * @returns 0 on success or the IOCtl error code on failure. + * @param pState pointer to the state structure. + * @param pMBlk pointer to the STREAMS message block structure. + */ +static int vbmsSolDispatchIOCtl(PVBMSSTATE pState, mblk_t *pMBlk) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + int iCmd = pIOCBlk->ioc_cmd, iCmdType = iCmd & (0xff << 8); + size_t cbBuffer; + enum IOCTLDIRECTION enmDirection; + + LogRelFlowFunc((DEVICE_NAME "::pIOCBlk=%p, iCmdType=%c, iCmd=0x%x\n", + pIOCBlk, (char) (iCmdType >> 8), (unsigned)iCmd)); + switch (iCmdType) + { + case MSIOC: + case VUIOC: + { + unsigned i; + + for (i = 0; i < RT_ELEMENTS(g_aVUIDIOCtlDescriptions); ++i) + if (g_aVUIDIOCtlDescriptions[i].iCmd == iCmd) + { + cbBuffer = g_aVUIDIOCtlDescriptions[i].cbBuffer; + enmDirection = g_aVUIDIOCtlDescriptions[i].enmDirection; + return vbmsSolHandleIOCtl(pState, pMBlk, + vbmsSolVUIDIOCtl, iCmd, + cbBuffer, enmDirection); + } + return EINVAL; + } + default: + return ENOTTY; + } +} + + +/* Helpers for vbmsSolHandleIOCtl. */ +static int vbmsSolHandleIOCtlData(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, int iCmd, + size_t cbCmd, + enum IOCTLDIRECTION enmDirection); + +static int vbmsSolHandleTransparentIOCtl(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, + int iCmd, size_t cbCmd, + enum IOCTLDIRECTION enmDirection); + +static int vbmsSolHandleIStrIOCtl(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, int iCmd); + +static void vbmsSolAcknowledgeIOCtl(mblk_t *pMBlk, int cbData, int rc) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + + pMBlk->b_datap->db_type = M_IOCACK; + pIOCBlk->ioc_count = cbData; + pIOCBlk->ioc_rval = rc; + pIOCBlk->ioc_error = 0; +} + +/** + * Generic code for handling STREAMS-specific IOCtl logic and boilerplate. It + * calls the IOCtl handler passed to it without the handler having to be aware + * of STREAMS structures, or whether this is a transparent (traditional) or an + * I_STR (using a STREAMS structure to describe the data) IOCtl. With the + * caveat that we only support transparent IOCtls which pass all data in a + * single buffer of a fixed size (I_STR IOCtls are restricted to a single + * buffer anyway, but the caller can choose the buffer size). + * @returns 0 on success or the IOCtl error code on failure. + * @param pState pointer to the state structure. + * @param pMBlk pointer to the STREAMS message block structure. + * @param pfnHandler pointer to the right IOCtl handler function for this + * IOCtl number. + * @param iCmd IOCtl command number. + * @param cbCmd size of the user space buffer for this IOCtl number, + * used for processing transparent IOCtls. Pass zero + * for IOCtls with no maximum buffer size (which will + * not be able to be handled as transparent) or with + * no argument. + * @param enmDirection data transfer direction of the IOCtl. + */ +static int vbmsSolHandleIOCtl(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, int iCmd, + size_t cbCmd, enum IOCTLDIRECTION enmDirection) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + + LogFlowFunc(("iCmd=0x%x, cbBuffer=%d, enmDirection=%d\n", + (unsigned)iCmd, (int)cbCmd, (int)enmDirection)); + if (pMBlk->b_datap->db_type == M_IOCDATA) + return vbmsSolHandleIOCtlData(pState, pMBlk, pfnHandler, iCmd, + cbCmd, enmDirection); + else if ( pMBlk->b_datap->db_type == M_IOCTL + && pIOCBlk->ioc_count == TRANSPARENT) + return vbmsSolHandleTransparentIOCtl(pState, pMBlk, pfnHandler, + iCmd, cbCmd, enmDirection); + else if (pMBlk->b_datap->db_type == M_IOCTL) + return vbmsSolHandleIStrIOCtl(pState, pMBlk, pfnHandler, iCmd); + return EINVAL; +} + + +/** + * Helper for vbmsSolHandleIOCtl. This rather complicated-looking + * code is basically the standard boilerplate for handling any streams IOCtl + * additional data, which we currently only use for transparent IOCtls. + * @copydoc vbmsSolHandleIOCtl + */ +static int vbmsSolHandleIOCtlData(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, int iCmd, + size_t cbCmd, + enum IOCTLDIRECTION enmDirection) +{ + struct copyresp *pCopyResp = (struct copyresp *)pMBlk->b_rptr; + + LogFlowFunc(("iCmd=0x%x, cbBuffer=%d, enmDirection=%d, cp_rval=%d, cp_private=%p\n", + (unsigned)iCmd, (int)cbCmd, (int)enmDirection, + (int)(uintptr_t)pCopyResp->cp_rval, + (void *)pCopyResp->cp_private)); + if (pCopyResp->cp_rval) /* cp_rval is a pointer used as a boolean. */ + return EAGAIN; + if ((pCopyResp->cp_private && enmDirection == BOTH) || enmDirection == IN) + { + size_t cbData = 0; + void *pvData = NULL; + int err; + + if (!pMBlk->b_cont) + return EINVAL; + pvData = pMBlk->b_cont->b_rptr; + err = pfnHandler(pState, iCmd, pvData, cbCmd, &cbData, NULL); + if (!err && enmDirection == BOTH) + mcopyout(pMBlk, NULL, cbData, pCopyResp->cp_private, NULL); + else if (!err && enmDirection == IN) + vbmsSolAcknowledgeIOCtl(pMBlk, 0, 0); + if ((err || enmDirection == IN) && pCopyResp->cp_private) + freemsg(pCopyResp->cp_private); + return err; + } + else + { + if (pCopyResp->cp_private) + freemsg(pCopyResp->cp_private); + AssertReturn(enmDirection == OUT || enmDirection == BOTH, EINVAL); + vbmsSolAcknowledgeIOCtl(pMBlk, 0, 0); + return 0; + } +} + +/** + * Helper for vbmsSolHandleIOCtl. This rather complicated-looking + * code is basically the standard boilerplate for handling transparent IOCtls, + * that is, IOCtls which are not re-packed inside STREAMS IOCtls. + * @copydoc vbmsSolHandleIOCtl + */ +int vbmsSolHandleTransparentIOCtl(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, int iCmd, + size_t cbCmd, + enum IOCTLDIRECTION enmDirection) +{ + int err = 0, rc = 0; + size_t cbData = 0; + + LogFlowFunc(("iCmd=0x%x, cbBuffer=%d, enmDirection=%d\n", + (unsigned)iCmd, (int)cbCmd, (int)enmDirection)); + if ( (enmDirection != NONE && !pMBlk->b_cont) + || enmDirection == UNSPECIFIED) + return EINVAL; + if (enmDirection == IN || enmDirection == BOTH) + { + void *pUserAddr = NULL; + /* We only need state data if there is something to copy back. */ + if (enmDirection == BOTH) + pUserAddr = *(void **)pMBlk->b_cont->b_rptr; + mcopyin(pMBlk, pUserAddr /* state data */, cbCmd, NULL); + } + else if (enmDirection == OUT) + { + mblk_t *pMBlkOut = allocb(cbCmd, BPRI_MED); + void *pvData; + + if (!pMBlkOut) + return EAGAIN; + pvData = pMBlkOut->b_rptr; + err = pfnHandler(pState, iCmd, pvData, cbCmd, &cbData, NULL); + if (!err) + mcopyout(pMBlk, NULL, cbData, NULL, pMBlkOut); + else + freemsg(pMBlkOut); + } + else + { + AssertReturn(enmDirection == NONE, EINVAL); + err = pfnHandler(pState, iCmd, NULL, 0, NULL, &rc); + if (!err) + vbmsSolAcknowledgeIOCtl(pMBlk, 0, rc); + } + return err; +} + +/** + * Helper for vbmsSolHandleIOCtl. This rather complicated-looking + * code is basically the standard boilerplate for handling any streams IOCtl. + * @copydoc vbmsSolHandleIOCtl + */ +static int vbmsSolHandleIStrIOCtl(PVBMSSTATE pState, mblk_t *pMBlk, + PFNVBMSSOLIOCTL pfnHandler, int iCmd) +{ + struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr; + uint_t cbBuffer = pIOCBlk->ioc_count; + void *pvData = NULL; + int err, rc = 0; + size_t cbData = 0; + + LogFlowFunc(("iCmd=0x%x, cbBuffer=%u, b_cont=%p\n", + (unsigned)iCmd, cbBuffer, (void *)pMBlk->b_cont)); + if (cbBuffer && !pMBlk->b_cont) + return EINVAL; + /* Repack the whole buffer into a single message block if needed. */ + if (cbBuffer) + { + err = miocpullup(pMBlk, cbBuffer); + if (err) + return err; + pvData = pMBlk->b_cont->b_rptr; + } + else if (pMBlk->b_cont) /* consms forgets to set ioc_count. */ + { + pvData = pMBlk->b_cont->b_rptr; + cbBuffer = pMBlk->b_cont->b_datap->db_lim + - pMBlk->b_cont->b_datap->db_base; + } + err = pfnHandler(pState, iCmd, pvData, cbBuffer, &cbData, &rc); + if (!err) + { + LogRelFlowFunc(("pMBlk=%p, pMBlk->b_datap=%p, pMBlk->b_rptr=%p\n", + pMBlk, pMBlk->b_datap, pMBlk->b_rptr)); + vbmsSolAcknowledgeIOCtl(pMBlk, cbData, rc); + } + return err; +} + + +/** + * Handle a VUID input device IOCtl. + * @copydoc FNVBMSSOLIOCTL + */ +static int vbmsSolVUIDIOCtl(PVBMSSTATE pState, int iCmd, void *pvData, + size_t cbBuffer, size_t *pcbData, int *prc) +{ + LogRelFlowFunc((DEVICE_NAME "::pvData=%p " /* no '\n' */, pvData)); + switch (iCmd) + { + case VUIDGFORMAT: + { + LogRelFlowFunc(("VUIDGFORMAT\n")); + if (cbBuffer < sizeof(int)) + return EINVAL; + *(int *)pvData = VUID_FIRM_EVENT; + *pcbData = sizeof(int); + return 0; + } + case VUIDSFORMAT: + LogRelFlowFunc(("VUIDSFORMAT\n")); + /* We define our native format to be VUID_FIRM_EVENT, so there + * is nothing more to do and we exit here on success or on + * failure. */ + return 0; + case VUIDGADDR: + case VUIDSADDR: + LogRelFlowFunc(("VUIDGADDR/VUIDSADDR\n")); + return ENOTTY; + case MSIOGETPARMS: + { + Ms_parms parms = { 0 }; + + LogRelFlowFunc(("MSIOGETPARMS\n")); + if (cbBuffer < sizeof(Ms_parms)) + return EINVAL; + *(Ms_parms *)pvData = parms; + *pcbData = sizeof(Ms_parms); + return 0; + } + case MSIOSETPARMS: + LogRelFlowFunc(("MSIOSETPARMS\n")); + return 0; + case MSIOSRESOLUTION: + { + Ms_screen_resolution *pResolution = (Ms_screen_resolution *)pvData; + int rc; + + LogRelFlowFunc(("MSIOSRESOLUTION, cbBuffer=%d, sizeof(Ms_screen_resolution)=%d\n", + (int) cbBuffer, + (int) sizeof(Ms_screen_resolution))); + if (cbBuffer < sizeof(Ms_screen_resolution)) + return EINVAL; + LogRelFlowFunc(("%dx%d\n", pResolution->width, + pResolution->height)); + pState->cMaxScreenX = pResolution->width - 1; + pState->cMaxScreenY = pResolution->height - 1; + /* Note: we don't disable this again until session close. */ + rc = VbglR0SetMouseStatus( VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE + | VMMDEV_MOUSE_NEW_PROTOCOL); + if (RT_SUCCESS(rc)) + return 0; + pState->cMaxScreenX = 0; + pState->cMaxScreenY = 0; + return ENODEV; + } + case MSIOBUTTONS: + { + LogRelFlowFunc(("MSIOBUTTONS\n")); + if (cbBuffer < sizeof(int)) + return EINVAL; + *(int *)pvData = 0; + *pcbData = sizeof(int); + return 0; + } + case VUIDGWHEELCOUNT: + { + LogRelFlowFunc(("VUIDGWHEELCOUNT\n")); + if (cbBuffer < sizeof(int)) + return EINVAL; + *(int *)pvData = 0; + *pcbData = sizeof(int); + return 0; + } + case VUIDGWHEELINFO: + case VUIDGWHEELSTATE: + case VUIDSWHEELSTATE: + LogRelFlowFunc(("VUIDGWHEELINFO/VUIDGWHEELSTATE/VUIDSWHEELSTATE\n")); + return EINVAL; + default: + LogRelFlowFunc(("Invalid IOCtl command %x\n", iCmd)); + return EINVAL; + } +} + + +#ifdef TESTCASE +int main(void) +{ + RTTEST hTest; + int rc = RTTestInitAndCreate("tstVBoxGuest-solaris", &hTest); + if (rc) + return rc; + RTTestBanner(hTest); + test_init(hTest); + testOpenClose(hTest); + testWPut(hTest); + + /* + * Summary. + */ + return RTTestSummaryAndDestroy(hTest); +} +#endif + diff --git a/src/VBox/Additions/solaris/Mouse/vboxms.conf b/src/VBox/Additions/solaris/Mouse/vboxms.conf new file mode 100644 index 00000000..eb180e18 --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/vboxms.conf @@ -0,0 +1,42 @@ +# $Id: vboxms.conf $ +## @file +# OpenSolaris Guest Mouse Driver Configuration +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# This needs to go into /usr/kernel/drv, +# while the 64-bit driver object goes into the amd64 +# subdirectory (32-bit drivers goes into the same +# directory). +# +name="vboxms" parent="pseudo" instance=0; diff --git a/src/VBox/Additions/solaris/Mouse/vboxmslnk.c b/src/VBox/Additions/solaris/Mouse/vboxmslnk.c new file mode 100644 index 00000000..12c5ad34 --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/vboxmslnk.c @@ -0,0 +1,217 @@ +/* $Id: vboxmslnk.c $ */ +/** @file + * VirtualBox Guest Additions Mouse Driver for Solaris: user space loader tool. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VBOXMSLNK_MUXID_FILE "/system/volatile/vboxmslnk.muxid" + +static const char *g_pszProgName; + + +static void vboxmslnk_fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void) vfprintf(stderr, fmt, ap); + if (fmt[strlen(fmt) - 1] != '\n') + (void) fprintf(stderr, " The error reported was: %s\n", strerror(errno)); + va_end(ap); + + exit(EXIT_FAILURE); +} + +static void vboxmslnk_start(bool fNoLogo) +{ + /* Open our pointer integration driver (vboxms). */ + int hVBoxMS = open("/dev/vboxms", O_RDWR); + if (hVBoxMS < 0) + vboxmslnk_fatal("Failed to open /dev/vboxms - please make sure that the node exists and that\n" + "you have permission to open it."); + + /* Open the Solaris virtual mouse driver (consms). */ + int hConsMS = open("/dev/mouse", O_RDWR); + if (hConsMS < 0) + vboxmslnk_fatal("Failed to open /dev/mouse - please make sure that the node exists and that\n" + "you have permission to open it."); + + /* Link vboxms to consms from below. What this means is that vboxms is + * added to the list of input sources multiplexed by consms, and vboxms + * will receive any control messages (such as information about guest + * resolution changes) sent to consms. The link can only be broken + * explicitly using the connection ID returned from the IOCtl. */ + int idConnection = ioctl(hConsMS, I_PLINK, hVBoxMS); + if (idConnection < 0) + vboxmslnk_fatal("Failed to add /dev/vboxms (the pointer integration driver) to /dev/mouse\n" + "(the Solaris virtual master mouse)."); + + (void) close(hVBoxMS); + (void) close(hConsMS); + + if (!fNoLogo) + (void) printf("Successfully enabled pointer integration. Connection ID number to the\n" + "Solaris virtual master mouse is:\n"); + (void) printf("%d\n", idConnection); + + /* Save the connection ID (aka mux ID) so it can be retrieved later. */ + FILE *fp = fopen(VBOXMSLNK_MUXID_FILE, "w"); + if (fp == NULL) + vboxmslnk_fatal("Failed to open %s for writing the connection ID.", VBOXMSLNK_MUXID_FILE); + int rc = fprintf(fp, "%d\n", idConnection); + if (rc <= 0) + vboxmslnk_fatal("Failed to write the connection ID to %s.", VBOXMSLNK_MUXID_FILE); + (void) fclose(fp); +} + +static void vboxmslnk_stop() +{ + /* Open the Solaris virtual mouse driver (consms). */ + int hConsMS = open("/dev/mouse", O_RDWR); + if (hConsMS < 0) + vboxmslnk_fatal("Failed to open /dev/mouse - please make sure that the node exists and that\n" + "you have permission to open it."); + + /* Open the vboxmslnk.muxid file and retrieve the saved mux ID. */ + FILE *fp = fopen(VBOXMSLNK_MUXID_FILE, "r"); + if (fp == NULL) + vboxmslnk_fatal("Failed to open %s for reading the connection ID.", VBOXMSLNK_MUXID_FILE); + int idConnection; + int rc = fscanf(fp, "%d\n", &idConnection); + if (rc <= 0) + vboxmslnk_fatal("Failed to read the connection ID from %s.", VBOXMSLNK_MUXID_FILE); + (void) fclose(fp); + (void) unlink(VBOXMSLNK_MUXID_FILE); + + /* Unlink vboxms from consms so that vboxms is able to be unloaded. */ + rc = ioctl(hConsMS, I_PUNLINK, idConnection); + if (rc < 0) + vboxmslnk_fatal("Failed to disconnect /dev/vboxms (the pointer integration driver) from\n" + "/dev/mouse (the Solaris virtual master mouse)."); + (void) close(hConsMS); +} + +static void vboxmslnk_usage() +{ + (void) printf("Usage:\n" + " %s [--nologo] <--start | --stop>\n" + " %s [-V|--version]\n\n" + " -V|--version print the tool version.\n" + " --nologo do not display the logo text and only output the connection\n" + " ID number needed to disable pointer integration\n" + " again.\n" + " --start Connect the VirtualBox pointer integration kernel module\n" + " to the Solaris mouse driver kernel module.\n" + " --stop Disconnect the VirtualBox pointer integration kernel module\n" + " from the Solaris mouse driver kernel module.\n" + " -h|--help display this help text.\n", + g_pszProgName, g_pszProgName); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) +{ + bool fShowVersion = false, fNoLogo = false, fStart = false, fStop = false; + int c; + + g_pszProgName = basename(argv[0]); + + static const struct option vboxmslnk_lopts[] = { + {"version", no_argument, 0, 'V' }, + {"nologo", no_argument, 0, 'n' }, + {"start", no_argument, 0, 's' }, + {"stop", no_argument, 0, 't' }, + {"help", no_argument, 0, 'h' }, + { 0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "Vh", vboxmslnk_lopts, NULL)) != -1) + { + switch (c) + { + case 'V': + fShowVersion = true; + break; + case 'n': + fNoLogo = true; + break; + case 's': + fStart = true; + break; + case 't': + fStop = true; + break; + case 'h': + default: + vboxmslnk_usage(); + } + } + + if ( (!fStart && !fStop && !fShowVersion) + || (fStart && fStop) + || (fShowVersion && (fNoLogo || fStart || fStop))) + vboxmslnk_usage(); + + if (fShowVersion) + { + (void) printf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + exit(EXIT_SUCCESS); + } + + if (!fNoLogo) + (void) printf(VBOX_PRODUCT + " Guest Additions utility for enabling Solaris pointer\nintegration Version " + VBOX_VERSION_STRING "\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + + if (fStart) + vboxmslnk_start(fNoLogo); + + if (fStop) + vboxmslnk_stop(); + + exit(EXIT_SUCCESS); +} diff --git a/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml b/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml new file mode 100644 index 00000000..59662494 --- /dev/null +++ b/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3