diff --git a/.gitignore b/.gitignore
index f2fcc56..9735bee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-SOURCES/tuned-2.14.0.tar.gz
+SOURCES/tuned-2.15.0.tar.gz
diff --git a/.tuned.metadata b/.tuned.metadata
index 0d3e106..f880b6a 100644
--- a/.tuned.metadata
+++ b/.tuned.metadata
@@ -1 +1 @@
-53140aba44d956fac19c37c2c0052835c1fdd7e9 SOURCES/tuned-2.14.0.tar.gz
+bfb3def0b687bbdae2b3e191d2fda46b3ffca1c0 SOURCES/tuned-2.15.0.tar.gz
diff --git a/SOURCES/tuned-2.14.0-amd-performance-regression-fix.patch b/SOURCES/tuned-2.14.0-amd-performance-regression-fix.patch
deleted file mode 100644
index e51870e..0000000
--- a/SOURCES/tuned-2.14.0-amd-performance-regression-fix.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From f511ad5d48f4f2ae3b2616463bc3a17bae5bdb90 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com>
-Date: Tue, 30 Jun 2020 15:39:18 +0200
-Subject: [PATCH] throughput-performance: fix performance regression on AMD
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-It turned out that disablement of the numa_balancing could result in
-upto 20% performance drop on some loads.
-
-Related: rhbz#1746957
-
-Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
----
- profiles/throughput-performance/tuned.conf | 1 -
- 1 file changed, 1 deletion(-)
-
-diff --git a/profiles/throughput-performance/tuned.conf b/profiles/throughput-performance/tuned.conf
-index f1a6f1a..3cc6fd1 100644
---- a/profiles/throughput-performance/tuned.conf
-+++ b/profiles/throughput-performance/tuned.conf
-@@ -85,4 +85,3 @@ type=sysctl
- uname_regex=x86_64
- cpuinfo_regex=${amd_cpuinfo_regex}
- kernel.sched_migration_cost_ns=5000000
--kernel.numa_balancing=0
--- 
-2.25.4
-
diff --git a/SOURCES/tuned-2.14.0-cmdline-duplication-with-BLS-and-grub2-mkconfig-fix.patch b/SOURCES/tuned-2.14.0-cmdline-duplication-with-BLS-and-grub2-mkconfig-fix.patch
deleted file mode 100644
index de5d529..0000000
--- a/SOURCES/tuned-2.14.0-cmdline-duplication-with-BLS-and-grub2-mkconfig-fix.patch
+++ /dev/null
@@ -1,95 +0,0 @@
-From a0fd433e874a0fbdd8c43ae42d25969550311b9c Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com>
-Date: Sun, 29 Nov 2020 21:34:15 +0100
-Subject: [PATCH] bootloader: fixed cmdline duplication with BLS and
- grub2-mkconfig
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-After application of this patch in order to get rid of the duplicated
-kernel command line options (which was duplicated due to the usage of
-the grub2-mkconfig), the grub2-mkconfig needs to be run again.
-We do not run it automatically in the spec file %post not to break
-users not using grub2-mkconfig.
-
-This fix expects that user has not modified the line containing the
-GRUB_CMDLINE_LINUX_DEFAULT option and the $tuned_params in the
-/etc/default/grub file. If user modified this line, the option
-duplication may still occure. In such case, please remove the
-$tuned_params from the /etc/default/grub by hand.
-
-Resolves: rhbz#1777874
-
-Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
----
- tuned/plugins/plugin_bootloader.py | 34 +++++++++++++++++++++++++++++-
- 1 file changed, 33 insertions(+), 1 deletion(-)
-
-diff --git a/tuned/plugins/plugin_bootloader.py b/tuned/plugins/plugin_bootloader.py
-index 8ca5b8a1..416df7d6 100644
---- a/tuned/plugins/plugin_bootloader.py
-+++ b/tuned/plugins/plugin_bootloader.py
-@@ -35,6 +35,7 @@ def _instance_init(self, instance):
- 		self._cmdline_val = ""
- 		self._initrd_val = ""
- 		self._grub2_cfg_file_names = self._get_grub2_cfg_files()
-+		self._bls = self._bls_enabled()
- 
- 	def _instance_cleanup(self, instance):
- 		pass
-@@ -88,6 +89,15 @@ def _get_grub2_cfg_files(self):
- 				cfg_files.append(f)
- 		return cfg_files
- 
-+	def _bls_enabled(self):
-+		grub2_default_env = self._cmd.read_file(consts.GRUB2_DEFAULT_ENV_FILE)
-+		if len(grub2_default_env) <= 0:
-+			log.info("cannot read '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
-+			return False
-+
-+		return re.search(r"^\s*GRUB_ENABLE_BLSCFG\s*=\s*\"?\s*[tT][rR][uU][eE]\s*\"?\s*$", grub2_default_env,
-+			flags = re.MULTILINE) is not None
-+
- 	def _patch_bootcmdline(self, d):
- 		return self._cmd.add_modify_option_in_file(consts.BOOT_CMDLINE_FILE, d)
- 
-@@ -154,6 +164,25 @@ def _grub2_default_env_patch(self):
- 			self._cmd.write_to_file(consts.GRUB2_DEFAULT_ENV_FILE, grub2_default_env)
- 		return True
- 
-+	def _grub2_default_env_unpatch(self):
-+		grub2_default_env = self._cmd.read_file(consts.GRUB2_DEFAULT_ENV_FILE)
-+		if len(grub2_default_env) <= 0:
-+			log.info("cannot read '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
-+			return False
-+
-+		write = False
-+		if re.search(r"^GRUB_CMDLINE_LINUX_DEFAULT=\"\$\{GRUB_CMDLINE_LINUX_DEFAULT:\+\$GRUB_CMDLINE_LINUX_DEFAULT \}\\\$" +
-+			consts.GRUB2_TUNED_VAR + "\"$", grub2_default_env, flags = re.MULTILINE):
-+				write = True
-+				cfg = re.sub(r"^GRUB_CMDLINE_LINUX_DEFAULT=\"\$\{GRUB_CMDLINE_LINUX_DEFAULT:\+\$GRUB_CMDLINE_LINUX_DEFAULT \}\\\$" +
-+					consts.GRUB2_TUNED_VAR + "\"$\n", "", grub2_default_env, flags = re.MULTILINE)
-+				if cfg[-1] != "\n":
-+					cfg += "\n"
-+		if write:
-+			log.debug("unpatching '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
-+			self._cmd.write_to_file(consts.GRUB2_DEFAULT_ENV_FILE, cfg)
-+		return True
-+
- 	def _grub2_cfg_patch(self, d):
- 		log.debug("patching grub.cfg")
- 		if not self._grub2_cfg_file_names:
-@@ -180,7 +209,10 @@ def _grub2_cfg_patch(self, d):
- 			if patch_initial:
- 				grub2_cfg_new = self._grub2_cfg_patch_initial(self._grub2_cfg_unpatch(grub2_cfg), d)
- 			self._cmd.write_to_file(f, grub2_cfg_new)
--		self._grub2_default_env_patch()
-+		if self._bls:
-+			self._grub2_default_env_unpatch()
-+		else:
-+			self._grub2_default_env_patch()
- 		return True
- 
- 	def _grub2_update(self):
-
diff --git a/SOURCES/tuned-2.14.0-realtime-virtual-host-remove-lapic-advancement.patch b/SOURCES/tuned-2.14.0-realtime-virtual-host-remove-lapic-advancement.patch
deleted file mode 100644
index 2c80a0e..0000000
--- a/SOURCES/tuned-2.14.0-realtime-virtual-host-remove-lapic-advancement.patch
+++ /dev/null
@@ -1,143 +0,0 @@
-diff --git a/profiles/realtime-virtual-host/find-lapictscdeadline-optimal.sh b/profiles/realtime-virtual-host/find-lapictscdeadline-optimal.sh
-deleted file mode 100755
-index 539c47e..0000000
---- a/profiles/realtime-virtual-host/find-lapictscdeadline-optimal.sh
-+++ /dev/null
-@@ -1,30 +0,0 @@
--#!/bin/bash
--
--: ${1?"Usage: $0 latency-file"}
--
--lines=`wc -l $1 | cut -f 1 -d " "`
--in_range=0
--prev_value=1
--for i in `seq 1 $lines`; do
--	a=`awk "NR==$i" $1 | cut -f 2 -d ":"`
--	value=$(($a*100/$prev_value))
--	if [ $value -ge 98 -a $value -le 102 ]; then
--		in_range=$(($in_range + 1))
--	else
--		in_range=0
--	fi
--	if [ $in_range -ge 2 ]; then
--		echo -n "optimal value for lapic_timer_advance_ns is: "
--		awk "NR==$(($i - 1))" $1 | cut -f 1 -d ":"
--		exit 0
--	fi
--	prev_value=$a
--done
--# if still decreasing, then use highest ns value
--if [ $value -le 99 ]; then
--	echo -n "optimal value for lapic_timer_advance_ns is: "
--	awk "NR==$(($i - 1))" $1 | cut -f 1 -d ":"
--	exit 0
--fi
--echo optimal not found
--exit 1
-diff --git a/profiles/realtime-virtual-host/script.sh b/profiles/realtime-virtual-host/script.sh
-index edae6c5..a11dac7 100755
---- a/profiles/realtime-virtual-host/script.sh
-+++ b/profiles/realtime-virtual-host/script.sh
-@@ -2,102 +2,13 @@
- 
- . /usr/lib/tuned/functions
- 
--CACHE_VALUE_FILE=./lapic_timer_adv_ns
--CACHE_CPU_FILE=./lapic_timer_adv_ns.cpumodel
--KVM_LAPIC_FILE=/sys/module/kvm/parameters/lapic_timer_advance_ns
- KTIMER_LOCKLESS_FILE=/sys/kernel/ktimer_lockless_check
--QEMU=$(type -P qemu-kvm || echo /usr/libexec/qemu-kvm)
--TSCDEADLINE_LATENCY="/usr/share/qemu-kvm/tscdeadline_latency.flat"
--[ -f "$TSCDEADLINE_LATENCY" ] || TSCDEADLINE_LATENCY="/usr/share/tuned-profiles-nfv-host-bin/tscdeadline_latency.flat"
--[ -f "$TSCDEADLINE_LATENCY" ] || TSCDEADLINE_LATENCY="/usr/share/tuned/tscdeadline_latency.flat"
--
--run_tsc_deadline_latency()
--{
--    dir=`mktemp -d`
--
--    for i in `seq 1000 500 7000`; do
--        echo $i > $KVM_LAPIC_FILE
--
--        unixpath=`mktemp`
--
--        chrt -f 1 $QEMU -S -enable-kvm -device pc-testdev \
--            -device isa-debug-exit,iobase=0xf4,iosize=0x4 \
--            -display none -serial stdio -device pci-testdev \
--            -kernel "$TSCDEADLINE_LATENCY"  \
--            -cpu host,tsc-deadline=on \
--            -mon chardev=char0,mode=readline \
--            -chardev socket,id=char0,nowait,path=$unixpath,server | grep latency | cut -f 2 -d ":" > $dir/out &
--
--        sleep 1s
--        pidofvcpu=`echo "info cpus" | ncat -U $unixpath | grep thread_id | cut -f 3 -d "=" | tr -d "\r"`
--        taskset -p -c $1 $pidofvcpu >/dev/null
--        echo "cont" | ncat -U $unixpath >/dev/null
--        wait
--
--        if [ ! -f $dir/out ]; then
--             die running $TSCDEADLINE_LATENCY failed
--        fi
--
--        tmp=$(wc -l $dir/out | awk '{ print $1 }')
--        if [ $tmp -eq 0 ]; then
--            die running $TSCDEADLINE_LATENCY failed
--        fi
--
--        A=0
--        while read l; do
--            A=$(($A+$l))
--        done < $dir/out
--
--        lines=`wc -l $dir/out | cut -f 1 -d " "`
--        ans=$(($A/$lines))
--        echo $i: $ans
--    done
--}
- 
- start() {
-     setup_kvm_mod_low_latency
- 
-     disable_ksm
- 
--    # If CPU model has changed, clean the cache
--    if [ -f $CACHE_CPU_FILE ]; then
--        curmodel=`cat /proc/cpuinfo | grep "model name" | cut -f 2 -d ":" | uniq`
--        if [ -z "$curmodel" ]; then
--            die failed to read CPU model
--        fi
--
--        genmodel=`cat $CACHE_CPU_FILE`
--
--        if [ "$curmodel" != "$genmodel" ]; then
--            rm -f $CACHE_VALUE_FILE
--            rm -f $CACHE_CPU_FILE
--        fi
--    fi
--
--    # If the cache is empty, find the best lapic_timer_advance_ns value
--    # and cache it
--
--    if [ ! -f $KVM_LAPIC_FILE ]; then
--        die $KVM_LAPIC_FILE not found
--    fi
--
--    if [ ! -f $CACHE_VALUE_FILE ]; then
--        if [ -f "$TSCDEADLINE_LATENCY" ]; then
--             tempdir=`mktemp -d`
--             isolatedcpu=`echo "$TUNED_isolated_cores_expanded" | cut -f 1 -d ","`
--             run_tsc_deadline_latency $isolatedcpu > $tempdir/lat.out
--             if ! ./find-lapictscdeadline-optimal.sh $tempdir/lat.out > $tempdir/opt.out; then
--                die could not find optimal latency
--             fi
--             echo `cat $tempdir/opt.out | cut -f 2 -d ":"` > $CACHE_VALUE_FILE
--             curmodel=`cat /proc/cpuinfo | grep "model name" | cut -f 2 -d ":" | uniq`
--             echo "$curmodel" > $CACHE_CPU_FILE
--        fi
--    fi
--
--    if [ -f $CACHE_VALUE_FILE ]; then
--        echo `cat $CACHE_VALUE_FILE` > $KVM_LAPIC_FILE
--    fi
-     systemctl start rt-entsk
- 
-     if [ -f $KTIMER_LOCKLESS_FILE ]; then
diff --git a/SOURCES/tuned-2.14.0-scheduler-isolated-cores-cgroups-fix.patch b/SOURCES/tuned-2.14.0-scheduler-isolated-cores-cgroups-fix.patch
deleted file mode 100644
index d839c51..0000000
--- a/SOURCES/tuned-2.14.0-scheduler-isolated-cores-cgroups-fix.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From daf02c380515b42db06d9f743070af5ab248a414 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com>
-Date: Fri, 3 Jul 2020 12:17:03 +0200
-Subject: [PATCH] scheduler: fix isolated_cores to work with cgroups
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-It seems cpuset.cpus needs to be initialized before writing to tasks.
-
-Related: rhbz#1784648
-
-Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
----
- tuned/plugins/plugin_scheduler.py | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/tuned/plugins/plugin_scheduler.py b/tuned/plugins/plugin_scheduler.py
-index 745ee4a..9ad9f54 100644
---- a/tuned/plugins/plugin_scheduler.py
-+++ b/tuned/plugins/plugin_scheduler.py
-@@ -106,6 +106,7 @@ class SchedulerPlugin(base.Plugin):
- 		# calculated by isolated_cores setter
- 		self._affinity = None
- 
-+		self._cgroup_affinity_initialized = False
- 		self._cgroup = None
- 		self._cgroups = collections.OrderedDict([(self._sanitize_cgroup_path(option[7:]), self._variables.expand(affinity))
- 			for option, affinity in instance.options.items() if option[:7] == "cgroup." and len(option) > 7])
-@@ -478,11 +479,14 @@ class SchedulerPlugin(base.Plugin):
- 			log.error("Unable to set affinity '%s' for cgroup '%s'" % (affinity, cgroup))
- 
- 	def _cgroup_set_affinity(self):
-+		if self._cgroup_affinity_initialized:
-+			return
- 		log.debug("Setting cgroups affinities")
- 		if self._affinity is not None and self._cgroup is not None and not self._cgroup in self._cgroups:
- 			self._cgroup_set_affinity_one(self._cgroup, self._affinity, backup = True)
- 		for cg in self._cgroups.items():
- 			self._cgroup_set_affinity_one(cg[0], cg[1], backup = True)
-+		self._cgroup_affinity_initialized = True
- 
- 	def _cgroup_restore_affinity(self):
- 		log.debug("Restoring cgroups affinities")
-@@ -920,6 +924,7 @@ class SchedulerPlugin(base.Plugin):
- 			return self._verify_all_irq_affinity(affinity, ignore_missing)
- 		elif enabling:
- 			if self._cgroup:
-+				self._cgroup_set_affinity()
- 				ps_affinity = "cgroup.%s" % self._cgroup
- 			else:
- 				ps_affinity = affinity
--- 
-2.25.4
-
diff --git a/SOURCES/tuned-2.15.0-netdev-queue-count.patch b/SOURCES/tuned-2.15.0-netdev-queue-count.patch
new file mode 100644
index 0000000..09e31d7
--- /dev/null
+++ b/SOURCES/tuned-2.15.0-netdev-queue-count.patch
@@ -0,0 +1,168 @@
+diff --git a/profiles/realtime/realtime-variables.conf b/profiles/realtime/realtime-variables.conf
+index c2da595..9d929e0 100644
+--- a/profiles/realtime/realtime-variables.conf
++++ b/profiles/realtime/realtime-variables.conf
+@@ -9,3 +9,11 @@
+ # kernel supports it.
+ #
+ # isolate_managed_irq=Y
++#
++#
++# Set the desired combined queue count value using the parameter provided
++# below. Ideally this should be set to the number of housekeeping CPUs i.e.,
++# in the example given below it is assumed that the system has 4 housekeeping
++# (non-isolated) CPUs.
++#
++# netdev_queue_count=4
+diff --git a/profiles/realtime/tuned.conf b/profiles/realtime/tuned.conf
+index 8eed36e..2400849 100644
+--- a/profiles/realtime/tuned.conf
++++ b/profiles/realtime/tuned.conf
+@@ -35,6 +35,9 @@ assert2=${f:assertion:isolated_cores contains online CPU(s):${isolated_cores_exp
+ isolate_managed_irq = ${isolate_managed_irq}
+ managed_irq=${f:regex_search_ternary:${isolate_managed_irq}:\b[y,Y,1,t,T]\b:managed_irq,domain,:}
+ 
++[net]
++channels=combined ${f:check_net_queue_count:${netdev_queue_count}}
++
+ [sysctl]
+ kernel.hung_task_timeout_secs = 600
+ kernel.nmi_watchdog = 0
+diff --git a/tuned/profiles/functions/function_check_net_queue_count.py b/tuned/profiles/functions/function_check_net_queue_count.py
+new file mode 100644
+index 0000000..eb54f98
+--- /dev/null
++++ b/tuned/profiles/functions/function_check_net_queue_count.py
+@@ -0,0 +1,22 @@
++import tuned.logs
++from . import base
++
++log = tuned.logs.get()
++
++class check_net_queue_count(base.Function):
++	"""
++	Checks whether the user has specified a queue count for net devices. If
++        not, return the number of housekeeping CPUs.
++	"""
++	def __init__(self):
++		# 1 argument
++		super(check_net_queue_count, self).__init__("check_net_queue_count", 1, 1)
++
++	def execute(self, args):
++		if not super(check_net_queue_count, self).execute(args):
++			return None
++		if args[0].isdigit():
++			return args[0]
++		(ret, out) = self._cmd.execute(["nproc"])
++		log.warn("net-dev queue count is not correctly specified, setting it to HK CPUs %s" % (out))
++		return out
+diff --git a/tuned/plugins/plugin_net.py b/tuned/plugins/plugin_net.py
+index 4d4c19e..a20d87e 100644
+--- a/tuned/plugins/plugin_net.py
++++ b/tuned/plugins/plugin_net.py
+@@ -122,6 +122,13 @@ class NetTuningPlugin(base.Plugin):
+ 			"rx-jumbo": None,
+ 			"tx": None }
+ 
++	@classmethod
++	def _get_config_options_channels(cls):
++		return { "rx": None,
++			"tx": None,
++			"other": None,
++			"combined": None }
++
+ 	@classmethod
+ 	def _get_config_options(cls):
+ 		return {
+@@ -132,6 +139,7 @@ class NetTuningPlugin(base.Plugin):
+ 			"coalesce": None,
+ 			"pause": None,
+ 			"ring": None,
++			"channels": None,
+ 		}
+ 
+ 	def _init_stats_and_idle(self, instance, device):
+@@ -282,7 +290,8 @@ class NetTuningPlugin(base.Plugin):
+ 		params = set(d.keys())
+ 		supported_getter = { "coalesce": self._get_config_options_coalesce, \
+ 				"pause": self._get_config_options_pause, \
+-				"ring": self._get_config_options_ring }
++				"ring": self._get_config_options_ring, \
++				"channels": self._get_config_options_channels }
+ 		supported = set(supported_getter[context]().keys())
+ 		if not params.issubset(supported):
+ 			log.error("unknown %s parameter(s): %s" % (context, str(params - supported)))
+@@ -313,6 +322,29 @@ class NetTuningPlugin(base.Plugin):
+ 		l = [x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2]
+ 		return dict(l)
+ 
++	# parse output of ethtool -l
++	def _parse_channels_parameters(self, s):
++		a = re.split(r"^Current hardware settings:$", s, flags=re.MULTILINE)
++		s = a[1]
++		s = self._cmd.multiple_re_replace(\
++				{"RX": "rx",
++				"TX": "tx",
++				"Other": "other",
++				"Combined": "combined"}, s)
++		l = s.split("\n")
++		l = [x for x in l if x != '']
++		l = [x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2]
++		return dict(l)
++
++	def _replace_channels_parameters(self, context, params_list, dev_params):
++		mod_params_list = []
++		if "combined" in params_list:
++			mod_params_list.extend(["rx", params_list[1], "tx", params_list[1]])
++		else:
++			cnt = str(max(int(params_list[1]), int(params_list[3])))
++			mod_params_list.extend(["combined", cnt])
++		return dict(list(zip(mod_params_list[::2], mod_params_list[1::2])))
++
+ 	def _check_device_support(self, context, parameters, device, dev_params):
+ 		"""Filter unsupported parameters and log warnings about it
+ 
+@@ -337,7 +369,8 @@ class NetTuningPlugin(base.Plugin):
+ 			parameters.pop(param, None)
+ 
+ 	def _get_device_parameters(self, context, device):
+-		context2opt = { "coalesce": "-c", "features": "-k", "pause": "-a", "ring": "-g" }
++		context2opt = { "coalesce": "-c", "features": "-k", "pause": "-a", "ring": "-g", \
++				"channels": "-l"}
+ 		opt = context2opt[context]
+ 		ret, value = self._cmd.execute(["ethtool", opt, device])
+ 		if ret != 0 or len(value) == 0:
+@@ -345,7 +378,8 @@ class NetTuningPlugin(base.Plugin):
+ 		context2parser = { "coalesce": self._parse_device_parameters, \
+ 				"features": self._parse_device_parameters, \
+ 				"pause": self._parse_pause_parameters, \
+-				"ring": self._parse_ring_parameters }
++				"ring": self._parse_ring_parameters, \
++				"channels": self._parse_channels_parameters }
+ 		parser = context2parser[context]
+ 		d = parser(value)
+ 		if context == "coalesce" and not self._check_parameters(context, d):
+@@ -362,10 +396,14 @@ class NetTuningPlugin(base.Plugin):
+ 		# check if device supports parameters and filter out unsupported ones
+ 		if dev_params:
+ 			self._check_device_support(context, d, device, dev_params)
++			# replace the channel parameters based on the device support
++			if context == "channels" and int(dev_params[next(iter(d))]) == 0:
++				d = self._replace_channels_parameters(context, self._cmd.dict2list(d), dev_params)
+ 
+ 		if not sim and len(d) != 0:
+ 			log.debug("setting %s: %s" % (context, str(d)))
+-			context2opt = { "coalesce": "-C", "features": "-K", "pause": "-A", "ring": "-G" }
++			context2opt = { "coalesce": "-C", "features": "-K", "pause": "-A", "ring": "-G", \
++                                "channels": "-L"}
+ 			opt = context2opt[context]
+ 			# ignore ethtool return code 80, it means parameter is already set
+ 			self._cmd.execute(["ethtool", opt, device] + self._cmd.dict2list(d), no_errors = [80])
+@@ -422,3 +460,7 @@ class NetTuningPlugin(base.Plugin):
+ 	@command_custom("ring", per_device = True)
+ 	def _ring(self, start, value, device, verify, ignore_missing):
+ 		return self._custom_parameters("ring", start, value, device, verify)
++
++	@command_custom("channels", per_device = True)
++	def _channels(self, start, value, device, verify, ignore_missing):
++		return self._custom_parameters("channels", start, value, device, verify)
diff --git a/SPECS/tuned.spec b/SPECS/tuned.spec
index 69e6b9b..3543252 100644
--- a/SPECS/tuned.spec
+++ b/SPECS/tuned.spec
@@ -33,8 +33,8 @@
 
 Summary: A dynamic adaptive system tuning daemon
 Name: tuned
-Version: 2.14.0
-Release: 3%{?prerel1}%{?dist}.2
+Version: 2.15.0
+Release: 2%{?prerel1}%{?dist}
 License: GPLv2+
 Source0: https://github.com/redhat-performance/%{name}/archive/v%{version}%{?prerel2}/%{name}-%{version}%{?prerel2}.tar.gz
 # RHEL-8 specific recommend.conf:
@@ -57,28 +57,25 @@ Requires: %{_py}-schedutils, %{_py}-linux-procfs, %{_py}-perf
 BuildRequires: python3-dbus, python3-gobject-base
 Requires: python3-dbus, python3-gobject-base
 %if 0%{?fedora} > 22 || 0%{?rhel} > 7
-Recommends: python3-dmidecode
+Recommends: dmidecode
 %endif
 %else
 # BuildRequires for 'make test'
 BuildRequires: dbus-python, pygobject3-base
 Requires: dbus-python, pygobject3-base
-%if 0%{?fedora} > 22 || 0%{?rhel} > 7
-Recommends: python-dmidecode
-%endif
 %endif
-Requires: virt-what, ethtool, gawk, hdparm
+Requires: virt-what, ethtool, gawk
 Requires: util-linux, dbus, polkit
 %if 0%{?fedora} > 22 || 0%{?rhel} > 7
+Recommends: dmidecode
+Recommends: hdparm
 Recommends: kernel-tools
+Recommends: kmod
 %endif
 %if 0%{?rhel} > 7
 Requires: python3-syspurpose
 %endif
-Patch0: tuned-2.14.0-amd-performance-regression-fix.patch
-Patch1: tuned-2.14.0-scheduler-isolated-cores-cgroups-fix.patch
-Patch2: tuned-2.14.0-realtime-virtual-host-remove-lapic-advancement.patch
-Patch3: tuned-2.14.0-cmdline-duplication-with-BLS-and-grub2-mkconfig-fix.patch
+Patch0: tuned-2.15.0-netdev-queue-count.patch
 
 %description
 The tuned package contains a daemon that tunes system settings dynamically.
@@ -224,19 +221,27 @@ Requires: %{name} = %{version}
 Additional tuned profiles mainly for backward compatibility with tuned 1.0.
 It can be also used to fine tune your system for specific scenarios.
 
+%package profiles-postgresql
+Summary: Additional tuned profile(s) targeted to PostgreSQL server loads
+Requires: %{name} = %{version}
+
+%description profiles-postgresql
+Additional tuned profile(s) targeted to PostgreSQL server loads.
+
 %prep
 %setup -q -n %{name}-%{version}%{?prerel2}
-
 %patch0 -p1
-%patch1 -p1
-%patch2 -p1
-%patch3 -p1
 
 # Replace the upstream recommend.conf with a RHEL-8-specific one
 rm -f recommend.conf
 cp -p %{SOURCE1} recommend.conf
 
 %build
+# Docs cannot be generated on RHEL now due to missing asciidoctor dependency
+# asciidoc doesn't seem to be compatible
+%if ! 0%{?rhel}
+make html %{make_python_arg}
+%endif
 
 %install
 make install DESTDIR=%{buildroot} DOCDIR=%{docdir} %{make_python_arg}
@@ -244,6 +249,11 @@ make install DESTDIR=%{buildroot} DOCDIR=%{docdir} %{make_python_arg}
 sed -i 's/\(dynamic_tuning[ \t]*=[ \t]*\).*/\10/' %{buildroot}%{_sysconfdir}/tuned/tuned-main.conf
 %endif
 
+%if ! 0%{?rhel}
+# manual
+make install-html DESTDIR=%{buildroot} DOCDIR=%{docdir}
+%endif
+
 # conditional support for grub2, grub2 is not available on all architectures
 # and tuned is noarch package, thus the following hack is needed
 mkdir -p %{buildroot}%{_datadir}/tuned/grub2
@@ -260,8 +270,13 @@ touch %{buildroot}%{_sysconfdir}/modprobe.d/kvm.rt.tuned.conf
 # validate desktop file
 desktop-file-validate %{buildroot}%{_datadir}/applications/tuned-gui.desktop
 
+# Run tests on RHEL > 7 or non RHEL
+# We cannot run tests on RHEL-7 because there is no python-mock package and
+# python-2.7 doesn't have mock built-in
+%if 0%{?rhel} > 7 || ! 0%{?rhel}
 %check
-make test
+make test %{make_python_arg}
+%endif
 
 %post
 %systemd_post tuned.service
@@ -308,11 +323,18 @@ if [ "$1" == 0 ]; then
   then
     for f in /boot/loader/entries/$MACHINE_ID-*.conf
     do
-      if [ -f "$f" -a "${f: -12}" != "-rescue.conf" ]
+      # Skip non-files and rescue entries
+      if [ ! -f "$f" -o "${f: -12}" == "-rescue.conf" ]
+      then
+        continue
+      fi
+      # Skip boom managed entries
+      if [[ "$f" =~ \w*-[0-9a-f]{7,}-.*-.*.conf ]]
       then
-        sed -i '/^\s*options\s\+.*\$tuned_params/ s/\s\+\$tuned_params\b//g' "$f" &>/dev/null || :
-        sed -i '/^\s*initrd\s\+.*\$tuned_initrd/ s/\s\+\$tuned_initrd\b//g' "$f" &>/dev/null || :
+        continue
       fi
+      sed -i '/^\s*options\s\+.*\$tuned_params/ s/\s\+\$tuned_params\b//g' "$f" &>/dev/null || :
+      sed -i '/^\s*initrd\s\+.*\$tuned_initrd/ s/\s\+\$tuned_initrd\b//g' "$f" &>/dev/null || :
     done
   fi
 fi
@@ -371,6 +393,7 @@ fi
 %exclude %{_prefix}/lib/tuned/realtime-virtual-host
 %exclude %{_prefix}/lib/tuned/cpu-partitioning
 %exclude %{_prefix}/lib/tuned/spectrumscale-ece
+%exclude %{_prefix}/lib/tuned/postgresql
 %{_prefix}/lib/tuned
 %dir %{_sysconfdir}/tuned
 %dir %{_sysconfdir}/tuned/recommend.d
@@ -484,15 +507,41 @@ fi
 %{_prefix}/lib/tuned/spindown-disk
 %{_mandir}/man7/tuned-profiles-compat.7*
 
+%files profiles-postgresql
+%{_prefix}/lib/tuned/postgresql
+%{_mandir}/man7/tuned-profiles-postgresql.7*
+
 %changelog
-* Tue Feb  2 2021 Jan Zerdik <jzerdik@redhat.com> - 2.14.0-3.2
-- bootloader: fixed cmdline duplication with BLS and grub2-mkconfig
-  Resolves: rhbz#1918995
+* Fri Feb 19 2021 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-2
+- realtime: added support for netdev_queue_count and extended plugin_net
+  resolves: rhbz#1846767
 
-* Fri Oct  2 2020 Jaroslav Škarvada <jskarvad@redhat.com> - 2.14.0-3.1
+* Thu Dec 17 2020 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-1
+- new release
+  - rebased tuned to latest upstream
+    related: rhbz#1874052
+
+* Tue Dec  1 2020 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-0.1.rc1
+- new release
+  - rebased tuned to latest upstream
+    resolves: rhbz#1874052
+  - added plugin service for linux services control
+    resolves: rhbz#1869991
+  - scheduler: added default_irq_smp_affinity option
+    resolves: rhbz#1896348
+  - bootloader: skip boom managed BLS snippets
+    resolves: rhbz#1901532
+  - scheduler: added perf_process_fork option to enable processing of fork
+    resolves: rhbz#1894610
+  - scheduler: added perf_mmap_pages option to set perf buffer size
+    resolves: rhbz#1890219
+  - bootloader: fixed cmdline duplication with BLS and grub2-mkconfig
+    resolves: rhbz#1777874
+
+* Thu Oct  1 2020 Jaroslav Škarvada <jskarvad@redhat.com> - 2.14.0-4
 - realtime-virtual-host: remove lapic advancement calculation and
   related qemu-kvm-tools-rhev requirement
-  Resolves: rhbz#1884544
+  Resolves: rhbz#1845717
 
 * Fri Jul  3 2020 Jaroslav Škarvada <jskarvad@redhat.com> - 2.14.0-3
 - scheduler: fixed isolated_cores to work with cgroups