diff --git a/supervisor/addons/model.py b/supervisor/addons/model.py index 1e9fc3120..a65c4a088 100644 --- a/supervisor/addons/model.py +++ b/supervisor/addons/model.py @@ -45,6 +45,7 @@ from ..const import ( ATTR_PORTS, ATTR_PORTS_DESCRIPTION, ATTR_PRIVILEGED, + ATTR_REALTIME, ATTR_REPOSITORY, ATTR_SCHEMA, ATTR_SERVICES, @@ -396,6 +397,11 @@ class AddonModel(CoreSysAttributes, ABC): """Return True if the add-on access to kernel modules.""" return self.data[ATTR_KERNEL_MODULES] + @property + def with_realtime(self) -> bool: + """Return True if the add-on need realtime schedule functions.""" + return self.data[ATTR_REALTIME] + @property def with_full_access(self) -> bool: """Return True if the add-on want full access to hardware.""" diff --git a/supervisor/addons/validate.py b/supervisor/addons/validate.py index bcfa6ab2c..385f02337 100644 --- a/supervisor/addons/validate.py +++ b/supervisor/addons/validate.py @@ -59,6 +59,7 @@ from ..const import ( ATTR_PORTS_DESCRIPTION, ATTR_PRIVILEGED, ATTR_PROTECTED, + ATTR_REALTIME, ATTR_REPOSITORY, ATTR_SCHEMA, ATTR_SERVICES, @@ -263,6 +264,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema( vol.Optional(ATTR_UART, default=False): vol.Boolean(), vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(), vol.Optional(ATTR_KERNEL_MODULES, default=False): vol.Boolean(), + vol.Optional(ATTR_REALTIME, default=False): vol.Boolean(), vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(), vol.Optional(ATTR_HASSIO_ROLE, default=ROLE_DEFAULT): vol.In(ROLE_ALL), vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(), diff --git a/supervisor/const.py b/supervisor/const.py index 726de1397..25ca284e4 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -289,6 +289,7 @@ ATTR_FREQUENCY = "frequency" ATTR_ACCESSPOINTS = "accesspoints" ATTR_UNHEALTHY = "unhealthy" ATTR_OTA = "ota" +ATTR_REALTIME = "realtime" PROVIDE_SERVICE = "provide" NEED_SERVICE = "need" diff --git a/supervisor/docker/addon.py b/supervisor/docker/addon.py index b6f8db3b2..6ae801e6b 100644 --- a/supervisor/docker/addon.py +++ b/supervisor/docker/addon.py @@ -250,11 +250,36 @@ class DockerAddon(DockerInterface): if self.addon.with_kernel_modules: capabilities.add(Capabilities.SYS_MODULE) + # Need schedule functions + if self.addon.with_realtime: + capabilities.add(Capabilities.SYS_NICE) + # Return None if no capabilities is present if capabilities: return [cap.value for cap in capabilities] return None + @property + def ulimits(self) -> Optional[List[docker.types.Ulimit]]: + """Generate ulimits for add-on.""" + limits: List[docker.types.Ulimit] = [] + + # Need schedule functions + if self.addon.with_realtime: + limits.append(docker.types.Ulimit(name="rtprio", soft=99)) + + # Return None if no capabilities is present + if limits: + return limits + return None + + @property + def cpu_rt_runtime(self) -> Optional[int]: + """Limit CPU real-time runtime in microseconds.""" + if self.addon.with_realtime: + return 950000 + return None + @property def volumes(self) -> Dict[str, Dict[str, str]]: """Generate volumes for mappings.""" @@ -416,6 +441,8 @@ class DockerAddon(DockerInterface): extra_hosts=self.network_mapping, device_cgroup_rules=self.cgroups_rules, cap_add=self.capabilities, + ulimits=self.ulimits, + cpu_rt_runtime=self.cpu_rt_runtime, security_opt=self.security_opt, environment=self.environment, volumes=self.volumes, diff --git a/supervisor/docker/audio.py b/supervisor/docker/audio.py index 4fcff056b..98a030fb6 100644 --- a/supervisor/docker/audio.py +++ b/supervisor/docker/audio.py @@ -2,6 +2,8 @@ import logging from typing import Dict, List +import docker + from ..const import ENV_TIME, MACHINE_ID from ..coresys import CoreSysAttributes from ..docker.const import Capabilities @@ -54,6 +56,11 @@ class DockerAudio(DockerInterface, CoreSysAttributes): """Generate needed capabilities.""" return [cap.value for cap in (Capabilities.SYS_NICE, Capabilities.SYS_RESOURCE)] + @property + def ulimits(self) -> List[docker.types.Ulimit]: + """Generate ulimits for audio.""" + return [docker.types.Ulimit(name="rtprio", soft=99)] + def _run(self) -> None: """Run Docker image. @@ -75,6 +82,8 @@ class DockerAudio(DockerInterface, CoreSysAttributes): hostname=self.name.replace("_", "-"), detach=True, cap_add=self.capabilities, + ulimits=self.ulimits, + cpu_rt_runtime=950000, device_cgroup_rules=self.cgroups_rules, environment={ENV_TIME: self.sys_config.timezone}, volumes=self.volumes,