Skip to content
Commits on Source (28)
......@@ -13,12 +13,13 @@ variables:
python3-gobject
python3-dbusmock
umockdev
DIST_DEPENDENCIES: xz
build:
before_script:
- dnf update -y --nogpgcheck && dnf install -y --nogpgcheck $DEPENDENCIES
script:
- meson -Dgtk_doc=true _build
- meson -Dgtk_doc=true -Dtests=true _build
- ninja -v -C _build
- ninja -v -C _build switcheroo-control-doc
- ninja -v -C _build install
......@@ -31,9 +32,9 @@ dist-job:
only:
- tags
before_script:
- dnf update -y --nogpgcheck && dnf install -y --nogpgcheck $DEPENDENCIES
- dnf update -y --nogpgcheck && dnf install -y --nogpgcheck $DEPENDENCIES $DIST_DEPENDENCIES
script:
- meson --buildtype release -Dgtk_doc=true _build
- meson --buildtype release -Dgtk_doc=true -Dtests=true _build
- cd _build
- ninja dist
- ninja switcheroo-control-doc
......
2.6
---
This release removes the recently added support for setting the GPU for
Vulkan apps as this broke sandboxed Vulkan apps. The support should come
back soon when functionality gets added to VulkanLoader.
This release also fixes non-x86 platforms not having a default GPU.
2.5
---
This release adds support for setting the GPU to use for Vulkan apps, on
systems with heterogenous GPUs.
This release also fixes the "--gpu" option not working as documented, and
installs the D-Bus configuration file in the correct location.
2.4
---
......
......@@ -10,7 +10,7 @@ configure_file(
install_data(
'net.hadess.SwitcherooControl.conf',
install_dir: sysconfdir / 'dbus-1/system.d',
install_dir: datadir / 'dbus-1/system.d',
)
install_data(
......
[Unit]
Description=Switcheroo Control Proxy service
Before=multi-user.target display-manager.target alsa-restore.service alsa-state.service
Before=multi-user.target display-manager.service alsa-restore.service alsa-state.service
[Service]
Type=dbus
......
project('switcheroo-control', 'c',
version : '2.4',
version : '2.6',
license: 'GPLv3+',
default_options : [
'buildtype=debugoptimized',
......@@ -13,7 +13,7 @@ cc = meson.get_compiler('c')
prefix = get_option('prefix')
libexecdir = prefix / get_option('libexecdir')
sysconfdir = get_option('sysconfdir')
datadir = get_option('datadir')
gnome = import('gnome')
......@@ -46,4 +46,19 @@ if get_option('gtk_doc')
subdir('docs')
endif
subdir('tests')
if get_option('tests')
# Python 3 required modules
python3_required_modules = ['dbus', 'dbusmock', 'gi']
python = import('python')
python3 = python.find_installation('python3')
foreach p : python3_required_modules
# Source: https://docs.python.org/3/library/importlib.html#checking-if-a-module-can-be-imported
script = 'import importlib.util; import sys; exit(1) if importlib.util.find_spec(\''+ p +'\') is None else exit(0)'
if run_command(python3, '-c', script, check: false).returncode() != 0
error('Python3 module \'' + p + '\' required for running tests but not found')
endif
endforeach
subdir('tests')
endif
......@@ -15,3 +15,9 @@ option('gtk_doc',
value: false,
description: 'Build docs',
)
option('tests',
description: 'Whether to run tests',
type: 'boolean',
value: false
)
......@@ -236,7 +236,7 @@ get_card_env (GUdevClient *client,
GUdevDevice *dev)
{
GPtrArray *array;
g_autoptr(GUdevDevice) parent;
g_autoptr(GUdevDevice) parent = NULL;
array = g_ptr_array_new_full (0, g_free);
......@@ -250,6 +250,10 @@ get_card_env (GUdevClient *client,
* https://download.nvidia.com/XFree86/Linux-x86_64/440.26/README/primerenderoffload.html */
g_ptr_array_add (array, g_strdup ("__NV_PRIME_RENDER_OFFLOAD"));
g_ptr_array_add (array, g_strdup ("1"));
/* Make sure Vulkan apps always select Nvidia GPUs */
g_ptr_array_add (array, g_strdup ("__VK_LAYER_NV_optimus"));
g_ptr_array_add (array, g_strdup ("NVIDIA_only"));
} else {
char *id;
......@@ -274,7 +278,7 @@ static char *
get_card_name (GUdevDevice *d)
{
const char *vendor, *product;
g_autoptr(GUdevDevice) parent;
g_autoptr(GUdevDevice) parent = NULL;
g_autofree char *renderer = NULL;
parent = g_udev_device_get_parent (d);
......@@ -302,7 +306,7 @@ bail:
static gboolean
get_card_is_default (GUdevDevice *d)
{
g_autoptr(GUdevDevice) parent;
g_autoptr(GUdevDevice) parent = NULL;
parent = g_udev_device_get_parent (d);
return g_udev_device_get_sysfs_attr_as_boolean (parent, "boot_vga");
......@@ -398,6 +402,12 @@ get_drm_cards (ControlData *data)
if (data->add_fake_cards)
add_fake_trident_card (cards);
/* Make sure the only card is the default */
if (cards->len == 1) {
CardData *card = cards->pdata[0];
card->is_default = TRUE;
}
return cards;
}
......
......@@ -108,7 +108,12 @@ def get_gpus():
raise ReferenceError
else:
# Move the first GPU to the front, it's the default
default_gpu = next(gpu for gpu in gpus if gpu['Default'])
try:
default_gpu = next(gpu for gpu in gpus if gpu['Default'])
except:
# The first GPU is the default if there's no default
default_gpu = gpus[0]
pass
gpus.remove(default_gpu)
gpus.insert(0, default_gpu)
return gpus
......@@ -166,7 +171,9 @@ elif command == 'version':
elif command == 'launch':
if len(args) == 0:
sys.exit(0)
if args[0] == '--gpu' or args[0] == '-g':
if args[0][:5] == '--gpu' or args[0] == '-g':
if args[0][:6] == '--gpu=':
args = args[0].split('=') + args[1:]
if len(args) == 2:
sys.exit(0)
if len(args) == 1:
......
......@@ -59,12 +59,10 @@ class Tests(dbusmock.DBusTestCase):
if os.access(os.path.join(builddir, 'src', 'switcheroo-control'), os.X_OK):
cls.daemon_path = os.path.join(builddir, 'src', 'switcheroo-control')
print('Testing binaries from local build tree (%s)' % cls.daemon_path)
cls.local_daemon = True
elif os.environ.get('UNDER_JHBUILD', False):
jhbuild_prefix = os.environ['JHBUILD_PREFIX']
cls.daemon_path = os.path.join(jhbuild_prefix, 'libexec', 'switcheroo-control')
print('Testing binaries from JHBuild (%s)' % cls.daemon_path)
cls.local_daemon = False
else:
cls.daemon_path = None
with open('/usr/lib/systemd/system/switcheroo-control.service') as f:
......@@ -73,7 +71,6 @@ class Tests(dbusmock.DBusTestCase):
cls.daemon_path = line.split('=', 1)[1].strip()
break
assert cls.daemon_path, 'could not determine daemon path from systemd .service file'
cls.local_daemon = False
print('Testing installed system binary (%s)' % cls.daemon_path)
# fail on CRITICALs on client side
......@@ -109,18 +106,18 @@ class Tests(dbusmock.DBusTestCase):
self.log = None
self.daemon = None
def tearDown(self):
del self.testbed
self.stop_daemon()
# on failures, print daemon log
errors = [x[1] for x in self._outcome.errors if x[1]]
if errors and self.log:
def run(self, result=None):
super(Tests, self).run(result)
if result and len(result.errors) + len(result.failures) > 0 and self.log:
with open(self.log.name) as f:
sys.stderr.write('\n-------------- daemon log: ----------------\n')
sys.stderr.write(f.read())
sys.stderr.write('------------------------------\n')
def tearDown(self):
del self.testbed
self.stop_daemon()
#
# Daemon control and D-BUS I/O
#
......@@ -138,10 +135,7 @@ class Tests(dbusmock.DBusTestCase):
env['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
self.log = tempfile.NamedTemporaryFile()
if os.getenv('VALGRIND') != None:
if self.local_daemon:
daemon_path = ['libtool', '--mode=execute', 'valgrind', self.daemon_path, '-v']
else:
daemon_path = ['valgrind', self.daemon_path, '-v']
daemon_path = ['valgrind', self.daemon_path, '-v']
else:
daemon_path = [self.daemon_path, '-v']
......@@ -275,6 +269,68 @@ class Tests(dbusmock.DBusTestCase):
'ID_PATH_TAG', 'pci-0000_01_00_0' ]
)
def add_nvidia_gpu(self):
parent = self.testbed.add_device('pci', 'NVidia VGA controller', None,
[ 'boot_vga', '0' ],
[ 'DRIVER', 'nvidia',
'PCI_CLASS', '30000',
'PCI_ID', '10DE:1C03',
'PCI_SUBSYS_ID', '1043:85AC'
'PCI_SLOT_NAME', '0000:01:00.0'
'MODALIAS', 'pci:v000010DEd00001C03sv00001043sd000085ACbc03sc00i00',
'ID_PCI_CLASS_FROM_DATABASE', 'Display controller',
'ID_PCI_SUBCLASS_FROM_DATABASE', 'VGA compatible controller',
'ID_PCI_INTERFACE_FROM_DATABASE', 'VGA controller',
'ID_VENDOR_FROM_DATABASE', 'NVIDIA Corporation',
'ID_MODEL_FROM_DATABASE', 'GP106 [GeForce GTX 1060 6GB]',
'FWUPD_GUID', '0x10de:0x85ac' ]
)
self.testbed.set_attribute_link(parent, 'driver', '../../nvidia')
self.testbed.add_device('drm', 'dri/card1', parent,
[],
[ 'DEVNAME', '/dev/dri/card1',
'ID_PATH', 'pci-0000:01:00.0',
'ID_PATH_TAG', 'pci-0000_01_00_0' ]
)
self.testbed.add_device('drm', 'dri/renderD129', parent,
[],
[ 'DEVNAME', '/dev/dri/renderD129',
'ID_PATH', 'pci-0000:01:00.0',
'ID_PATH_TAG', 'pci-0000_01_00_0' ]
)
def add_vc4_gpu(self):
parent = self.testbed.add_device('platform', 'VC4 platform device', None,
[],
[ 'DRIVER', 'vc4-drm',
'OF_NAME', 'gpu',
'OF_FULLNAME', '/soc/gpu',
'OF_COMPATIBLE_0', 'brcm,bcm2835-vc4',
'OF_COMPATIBLE_N', '1',
'MODALIAS', 'of:NgpuT(null)Cbrcm,bcm2835-vc4',
'ID_PATH', 'platform-soc:gpu',
'ID_PATH_TAG', 'platform-soc_gpu' ]
)
self.testbed.set_attribute_link(parent, 'driver', '../../vc4-drm')
self.testbed.add_device('drm', 'dri/card1', parent,
[],
[ 'DEVNAME', '/dev/dri/card1',
'ID_PATH', 'platform-soc:gpu',
'ID_PATH_TAG', 'platform-soc_gpu' ]
)
self.testbed.add_device('drm', 'dri/renderD129', parent,
[],
[ 'DEVNAME', '/dev/dri/renderD129',
'ID_PATH', 'platform-soc:gpu',
'ID_PATH_TAG', 'platform-soc_gpu' ]
)
#
# Actual test cases
#
......@@ -302,6 +358,28 @@ class Tests(dbusmock.DBusTestCase):
self.stop_daemon()
def test_rpi(self):
self.add_vc4_gpu()
self.start_daemon()
self.assertEqual(self.get_dbus_property('HasDualGpu'), False)
self.assertEqual(self.get_dbus_property('NumGPUs'), 1)
gpus = self.get_dbus_property('GPUs')
self.assertEqual(len(gpus), 1)
self.assertEqual(gpus[0]['Name'], 'Unknown Graphics Controller')
sc_env = gpus[0]['Environment']
self.assertEqual(len(sc_env), 2)
self.assertEqual(sc_env[0], 'DRI_PRIME')
self.assertEqual(sc_env[1], 'platform-soc_gpu')
self.assertEqual(gpus[0]['Default'], True)
# process = subprocess.Popen(['gdbus', 'introspect', '--system', '--dest', 'net.hadess.SwitcherooControl', '--object-path', '/net/hadess/SwitcherooControl'])
# print (self.get_dbus_property('GPUs'))
self.stop_daemon()
def test_dual_open_source(self):
'''dual open source devices'''
......@@ -335,7 +413,7 @@ class Tests(dbusmock.DBusTestCase):
self.stop_daemon()
def test_dual_open_source(self):
def test_dual_open_source_with_ttm(self):
'''dual open source devices'''
self.add_intel_gpu()
......@@ -366,6 +444,44 @@ class Tests(dbusmock.DBusTestCase):
self.stop_daemon()
def test_dual_proprietary(self):
'''oss intel + nvidia blob'''
self.add_intel_gpu()
self.add_nvidia_gpu()
self.start_daemon()
self.assertEqual(self.get_dbus_property('HasDualGpu'), True)
self.assertEqual(self.get_dbus_property('NumGPUs'), 2)
gpus = self.get_dbus_property('GPUs')
self.assertEqual(len(gpus), 2)
gpu1 = gpus[0]
self.assertEqual(gpu1['Name'], 'NVIDIA Corporation GP106 [GeForce GTX 1060 6GB]')
self.assertEqual(gpu1['Default'], False)
gpu2 = gpus[1]
self.assertEqual(gpu2['Name'], 'Intel® UHD Graphics 620 (Kabylake GT2)')
self.assertEqual(gpu2['Default'], True)
sc_env = gpu1['Environment']
self.assertIn('__GLX_VENDOR_LIBRARY_NAME', sc_env)
self.assertIn('__NV_PRIME_RENDER_OFFLOAD', sc_env)
self.assertIn('__VK_LAYER_NV_optimus', sc_env)
def get_sc_env(name):
i = sc_env.index(name)
return sc_env[i+1]
self.assertEqual(get_sc_env('__GLX_VENDOR_LIBRARY_NAME'), 'nvidia')
self.assertEqual(get_sc_env('__NV_PRIME_RENDER_OFFLOAD'), '1')
self.assertEqual(get_sc_env('__VK_LAYER_NV_optimus'), 'NVIDIA_only')
self.stop_daemon()
def test_dual_hotplug(self):
'''dual open source devices'''
......@@ -384,6 +500,32 @@ class Tests(dbusmock.DBusTestCase):
self.stop_daemon()
def test_cmdline_tool(self):
'''test the command-line tool'''
self.add_intel_gpu()
self.add_nouveau_gpu()
self.start_daemon()
builddir = os.getenv('top_builddir', '.')
tool_path = os.path.join(builddir, 'src', 'switcherooctl')
out = subprocess.run([tool_path], capture_output=True)
self.assertEqual(out.returncode, 0, "'switcherooctl' call failed")
self.assertEqual(out.stdout, b'Device: 0\n Name: Intel\xc2\xae UHD Graphics 620 (Kabylake GT2)\n Default: yes\n Environment: DRI_PRIME=pci-0000_00_02_0\n\nDevice: 1\n Name: GM108M [GeForce 930MX]\n Default: no\n Environment: DRI_PRIME=pci-0000_01_00_0\n')
out = subprocess.run([tool_path, 'launch', '--gpu', '0', 'env'], capture_output=True)
self.assertEqual(out.returncode, 0, "'switcherooctl launch --gpu 0' failed")
assert('DRI_PRIME=pci-0000_00_02_0' in str(out.stdout))
out = subprocess.run([tool_path, 'launch', '--gpu', '1', 'env'], capture_output=True)
self.assertEqual(out.returncode, 0, "'switcherooctl launch --gpu 1' failed")
assert('DRI_PRIME=pci-0000_01_00_0' in str(out.stdout))
out = subprocess.run([tool_path, 'launch', '--gpu=1', 'env'], capture_output=True)
self.assertEqual(out.returncode, 0, "'switcherooctl launch --gpu=1' failed")
assert('DRI_PRIME=pci-0000_01_00_0' in str(out.stdout))
#
# Helper methods
#
......
integration_test = find_program('integration-test')
envs = environment()
envs.set ('top_builddir', meson.build_root())
test('switcheroo-control-integration-test',
integration_test,
env: envs
)
python3 = find_program('python3')
unittest_inspector = find_program('unittest_inspector.py')
r = run_command(unittest_inspector, files('integration-test.py'), check: true)
unit_tests = r.stdout().strip().split('\n')
foreach ut: unit_tests
ut_args = files('integration-test.py')
ut_args += ut
test(ut,
python3,
args: ut_args,
env: envs,
)
endforeach
#! /usr/bin/env python3
# Copyright © 2020, Canonical Ltd
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
# Authors:
# Marco Trevisan <marco.trevisan@canonical.com>
import argparse
import importlib.util
import inspect
import os
import unittest
def list_tests(module):
tests = []
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, unittest.TestCase):
cases = unittest.defaultTestLoader.getTestCaseNames(obj)
tests += [ (obj, '{}.{}'.format(name, t)) for t in cases ]
return tests
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('unittest_source', type=argparse.FileType('r'))
args = parser.parse_args()
source_path = args.unittest_source.name
spec = importlib.util.spec_from_file_location(
os.path.basename(source_path), source_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for machine, human in list_tests(module):
print(human)