summaryrefslogtreecommitdiffstats
path: root/meta-egvirt/recipes-kernel/linux/linux-yocto/virtio-scmi/0010-firmware-arm_scmi-Add-virtio-transport.patch
blob: 35ea04f8f59f31e8a0e01a0cb38a9843ddfc05ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
From 7b9f3fe0766d6d36ca7465e54754df51ca175079 Mon Sep 17 00:00:00 2001
From: Igor Skalkin <igor.skalkin@opensynergy.com>
Date: Thu, 5 Nov 2020 22:21:16 +0100
Subject: [PATCH] firmware: arm_scmi: Add virtio transport

This transport enables accessing an SCMI platform as a virtio device.

Implement an SCMI virtio driver according to the virtio SCMI device spec
patch v5 [1]. Virtio device id 32 has been reserved for the SCMI device
[2].

The virtio transport has one tx channel (virtio cmdq, A2P channel) and
at most one rx channel (virtio eventq, P2A channel).

The following feature bit defined in [1] is not implemented:
VIRTIO_SCMI_F_SHARED_MEMORY.

After the preparatory patches, implement the virtio transport as
paraphrased:

Only support a single arm-scmi device (which is consistent with the SCMI
spec). Call scmi-virtio init from arm-scmi module init. During the
arm-scmi probing, link to the first probed scmi-virtio device. Defer
arm-scmi probing if no scmi-virtio device is bound yet.

Use the scmi_xfer tx/rx buffers for data exchange with the virtio device
in order to avoid redundant maintenance of additional buffers. Allocate
the buffers in the SCMI transport, and prepend room for a small header
used by the virtio transport to the tx/rx buffers.

For simplicity, restrict the number of messages which can be pending
simultaneously according to the virtqueue capacity. (The virtqueue sizes
are negotiated with the virtio device.)

As soon as rx channel message buffers are allocated or have been read
out by the arm-scmi driver, feed them to the virtio device.

Since some virtio devices may not have the short response time exhibited
by SCMI platforms using other transports, set a generous response
timeout.

Limitations:

Do not adjust the other SCMI timeouts for delayed response and polling
for now, since these timeouts are only relevant in special cases which
are not yet deemed relevant for this transport.

To do (as discussed in the cover letter):

- Avoid re-use of buffers still being used by the virtio device on
  timeouts.

- Avoid race conditions on receiving messages during/after channel free
  on driver probe failure or remove.

[1] https://lists.oasis-open.org/archives/virtio-comment/202005/msg00096.html
[2] https://www.oasis-open.org/committees/ballot.php?id=3496

Co-developed-by: Peter Hilber <peter.hilber@opensynergy.com>
Signed-off-by: Peter Hilber <peter.hilber@opensynergy.com>
Signed-off-by: Igor Skalkin <igor.skalkin@opensynergy.com>
Signed-off-by: Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>
---
 MAINTAINERS                        |   1 +
 drivers/firmware/Kconfig           |  12 +-
 drivers/firmware/arm_scmi/Makefile |   1 +
 drivers/firmware/arm_scmi/common.h |  14 +
 drivers/firmware/arm_scmi/driver.c |  11 +
 drivers/firmware/arm_scmi/virtio.c | 493 +++++++++++++++++++++++++++++
 include/uapi/linux/virtio_ids.h    |   1 +
 include/uapi/linux/virtio_scmi.h   |  41 +++
 8 files changed, 573 insertions(+), 1 deletion(-)
 create mode 100644 drivers/firmware/arm_scmi/virtio.c
 create mode 100644 include/uapi/linux/virtio_scmi.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 49772b741967..d223a5c3f465 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16973,6 +16973,7 @@ F:	drivers/firmware/arm_scpi.c
 F:	drivers/reset/reset-scmi.c
 F:	include/linux/sc[mp]i_protocol.h
 F:	include/trace/events/scmi.h
+F:	include/uapi/linux/virtio_scmi.h
 
 SYSTEM RESET/SHUTDOWN DRIVERS
 M:	Sebastian Reichel <sre@kernel.org>
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 30a85d38d0c0..82e97904bcae 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -9,7 +9,7 @@ menu "Firmware Drivers"
 config ARM_SCMI_PROTOCOL
 	tristate "ARM System Control and Management Interface (SCMI) Message Protocol"
 	depends on ARM || ARM64 || COMPILE_TEST
-	depends on ARM_SCMI_HAVE_SHMEM
+	depends on ARM_SCMI_HAVE_SHMEM || VIRTIO_SCMI
 	help
 	  ARM System Control and Management Interface (SCMI) protocol is a
 	  set of operating system-independent software interfaces that are
@@ -34,6 +34,16 @@ config ARM_SCMI_HAVE_SHMEM
 	  This declares whether a shared memory based transport for SCMI is
 	  available.
 
+config VIRTIO_SCMI
+	bool "Virtio transport for SCMI"
+	default n
+	depends on VIRTIO
+	help
+	  This enables the virtio based transport for SCMI.
+
+	  If you want to use the ARM SCMI protocol between the virtio guest and
+	  a host providing a virtio SCMI device, answer Y.
+
 config ARM_SCMI_POWER_DOMAIN
 	tristate "SCMI power domain driver"
 	depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 3cc7fa40a464..25caea5e1969 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -4,6 +4,7 @@ scmi-driver-y = driver.o notify.o
 scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
 scmi-transport-$(CONFIG_MAILBOX) += mailbox.o
 scmi-transport-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smc.o
+scmi-transport-$(CONFIG_VIRTIO_SCMI) += virtio.o
 scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o
 scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \
 		    $(scmi-transport-y)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 13c9ac176b23..b46dfe84e78b 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -165,6 +165,17 @@ int scmi_base_protocol_init(struct scmi_handle *h);
 int __init scmi_bus_init(void);
 void __exit scmi_bus_exit(void);
 
+#ifdef CONFIG_VIRTIO_SCMI
+int __init virtio_scmi_init(void);
+void __exit virtio_scmi_exit(void);
+#else
+static inline int __init virtio_scmi_init(void)
+{
+	return 0;
+}
+#define virtio_scmi_exit() do { } while (0)
+#endif
+
 #define DECLARE_SCMI_REGISTER_UNREGISTER(func)		\
 	int __init scmi_##func##_register(void);	\
 	void __exit scmi_##func##_unregister(void)
@@ -263,6 +274,9 @@ extern const struct scmi_desc scmi_mailbox_desc;
 #ifdef CONFIG_HAVE_ARM_SMCCC
 extern const struct scmi_desc scmi_smc_desc;
 #endif
+#ifdef CONFIG_VIRTIO_SCMI
+extern const struct scmi_desc scmi_virtio_desc;
+#endif
 
 int scmi_set_transport_info(struct device *dev, void *transport_info);
 void *scmi_get_transport_info(struct device *dev);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 93ce17bb4079..540a55285349 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -996,6 +996,9 @@ static const struct of_device_id scmi_of_match[] = {
 #endif
 #ifdef CONFIG_HAVE_ARM_SMCCC_DISCOVERY
 	{ .compatible = "arm,scmi-smc", .data = &scmi_smc_desc},
+#endif
+#ifdef CONFIG_VIRTIO_SCMI
+	{ .compatible = "arm,scmi-virtio", .data = &scmi_virtio_desc},
 #endif
 	{ /* Sentinel */ },
 };
@@ -1014,8 +1017,14 @@ static struct platform_driver scmi_driver = {
 
 static int __init scmi_driver_init(void)
 {
+	int ret;
+
 	scmi_bus_init();
 
+	ret = virtio_scmi_init();
+	if (ret)
+		return ret;
+
 	scmi_clock_register();
 	scmi_perf_register();
 	scmi_power_register();
@@ -1038,6 +1047,8 @@ static void __exit scmi_driver_exit(void)
 	scmi_sensors_unregister();
 	scmi_system_unregister();
 
+	virtio_scmi_exit();
+
 	platform_driver_unregister(&scmi_driver);
 }
 module_exit(scmi_driver_exit);
diff --git a/drivers/firmware/arm_scmi/virtio.c b/drivers/firmware/arm_scmi/virtio.c
new file mode 100644
index 000000000000..f70aa72f34f1
--- /dev/null
+++ b/drivers/firmware/arm_scmi/virtio.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Virtio Transport driver for Arm System Control and Management Interface
+ * (SCMI).
+ *
+ * Copyright (C) 2020 OpenSynergy.
+ */
+
+/**
+ * DOC: Theory of Operation
+ *
+ * The scmi-virtio transport implements a driver for the virtio SCMI device
+ * proposed in virtio spec patch v5[1].
+ *
+ * There is one tx channel (virtio cmdq, A2P channel) and at most one rx
+ * channel (virtio eventq, P2A channel). Each channel is implemented through a
+ * virtqueue. Access to each virtqueue is protected by a spinlock.
+ *
+ * This SCMI transport uses the scmi_xfer tx/rx buffers for data exchange with
+ * the virtio device to avoid maintenance of additional buffers.
+ *
+ * [1] https://lists.oasis-open.org/archives/virtio-comment/202005/msg00096.html
+ */
+
+#include <linux/errno.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_ids.h>
+#include <uapi/linux/virtio_scmi.h>
+
+#include "common.h"
+
+#define VIRTIO_SCMI_MAX_MSG_SIZE 128 /* Value may be increased. */
+#define DESCR_PER_TX_MSG 2
+
+struct scmi_vio_channel {
+	spinlock_t lock;
+	struct virtqueue *vqueue;
+	struct scmi_chan_info *cinfo;
+	u8 is_rx;
+};
+
+union virtio_scmi_input {
+	__virtio32 hdr;
+	struct virtio_scmi_response response;
+	struct virtio_scmi_notification notification;
+};
+
+struct scmi_vio_msg {
+	struct virtio_scmi_request *request;
+	union virtio_scmi_input *input;
+	u8 completed;
+};
+
+static int scmi_vio_populate_vq_rx(struct scmi_vio_channel *vioch,
+				   struct scmi_xfer *xfer)
+{
+	struct scatterlist sg_in;
+	struct scmi_vio_msg *msg = xfer->extra_data;
+	int rc;
+
+	msg->completed = false;
+
+	sg_init_one(&sg_in, msg->input,
+		    sizeof(*msg->input) + VIRTIO_SCMI_MAX_MSG_SIZE);
+
+	rc = virtqueue_add_inbuf(vioch->vqueue, &sg_in, 1, xfer, GFP_ATOMIC);
+	if (rc)
+		dev_err(vioch->cinfo->dev, "%s() rc=%d\n", __func__, rc);
+	else
+		virtqueue_kick(vioch->vqueue);
+
+	return rc;
+}
+
+static void scmi_vio_complete_cb(struct virtqueue *vqueue)
+{
+	struct scmi_vio_channel *vioch = vqueue->priv;
+	unsigned long iflags;
+	unsigned int length;
+
+	spin_lock_irqsave(&vioch->lock, iflags);
+
+	do {
+		struct scmi_xfer *xfer;
+
+		virtqueue_disable_cb(vqueue);
+
+		while ((xfer = virtqueue_get_buf(vqueue, &length))) {
+			struct scmi_vio_msg *msg = xfer->extra_data;
+			u32 msg_hdr =
+				virtio32_to_cpu(vqueue->vdev, msg->input->hdr);
+			u8 msg_type = MSG_XTRACT_TYPE(msg_hdr);
+
+			if (!vioch->is_rx) { /* tx queue response */
+				msg->completed = true;
+				xfer->rx.len =
+					length - sizeof(msg->input->response);
+				if (!xfer->hdr.poll_completion)
+					scmi_rx_callback(vioch->cinfo, msg_hdr);
+				continue;
+			}
+
+			/* rx queue - notification or delayed response */
+			switch (msg_type) {
+			case MSG_TYPE_NOTIFICATION:
+				xfer->rx.len = length -
+					       sizeof(msg->input->notification);
+				xfer->rx.buf = msg->input->notification.data;
+				break;
+			case MSG_TYPE_DELAYED_RESP:
+				xfer->rx.len =
+					length - sizeof(msg->input->response);
+				xfer->rx.buf = msg->input->response.data;
+				break;
+			default:
+				dev_warn_once(vioch->cinfo->dev,
+					      "rx: unknown message_type %d\n",
+					      msg_type);
+				scmi_vio_populate_vq_rx(vioch, xfer);
+				continue;
+			}
+
+			scmi_rx_callback(vioch->cinfo, msg_hdr);
+			scmi_vio_populate_vq_rx(vioch, xfer);
+		}
+
+		if (unlikely(virtqueue_is_broken(vqueue)))
+			break;
+	} while (!virtqueue_enable_cb(vqueue));
+
+	spin_unlock_irqrestore(&vioch->lock, iflags);
+}
+
+static const char *const scmi_vio_vqueue_names[] = { "tx", "rx" };
+
+static vq_callback_t *scmi_vio_complete_callbacks[] = {
+	scmi_vio_complete_cb,
+	scmi_vio_complete_cb
+};
+
+static int scmi_vio_match_any_dev(struct device *dev, const void *data)
+{
+	(void)dev;
+	(void)data;
+
+	return 1;
+}
+
+static struct virtio_driver virtio_scmi_driver; /* Forward declaration */
+
+static int virtio_link_supplier(struct device *dev)
+{
+	struct device *vdev = driver_find_device(
+		&virtio_scmi_driver.driver, NULL, NULL, scmi_vio_match_any_dev);
+
+	if (!vdev) {
+		dev_notice_once(
+			dev,
+			"Deferring probe after not finding a bound scmi-virtio device\n");
+		return -EPROBE_DEFER;
+	}
+
+	/*
+	 * Add plain device link for completeness. It might have no effect
+	 * beyond sysfs.
+	 */
+	if (!device_link_add(dev, vdev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
+		put_device(vdev);
+		dev_err(dev, "Adding link to supplier virtio device failed\n");
+		return -ECANCELED;
+	}
+
+	put_device(vdev);
+	return scmi_set_transport_info(dev, dev_to_virtio(vdev));
+}
+
+static bool virtio_chan_available(struct device *dev, int idx)
+{
+	struct virtio_device *vdev;
+	struct scmi_vio_channel **vioch;
+
+	/* scmi-virtio doesn't support per-protocol channels */
+	if (is_scmi_protocol_device(dev))
+		return false;
+
+	vdev = scmi_get_transport_info(dev);
+	if (!vdev)
+		return false;
+
+	vioch = vdev->priv;
+	if (!vioch)
+		return false;
+
+	return vioch[idx] && vioch[idx]->vqueue;
+}
+
+static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
+			     bool tx)
+{
+	struct virtio_device *vdev;
+	struct scmi_vio_channel **vioch;
+	int vioch_index = tx ? VIRTIO_SCMI_VQ_TX : VIRTIO_SCMI_VQ_RX;
+
+	/* scmi-virtio doesn't support per-protocol channels */
+	if (is_scmi_protocol_device(dev))
+		return -1;
+
+	vdev = scmi_get_transport_info(dev);
+	if (!vdev)
+		return -1;
+
+	vioch = vdev->priv;
+	if (!vioch) {
+		dev_err(dev, "Data from scmi-virtio probe not found\n");
+		return -1;
+	}
+	cinfo->transport_info = vioch[vioch_index];
+	vioch[vioch_index]->cinfo = cinfo;
+
+	return 0;
+}
+
+static int virtio_chan_free(int id, void *p, void *data)
+{
+	struct scmi_chan_info *cinfo = p;
+	struct scmi_vio_channel *vioch = cinfo->transport_info;
+
+	if (vioch) {
+		cinfo->transport_info = NULL;
+		kfree(vioch);
+	}
+
+	scmi_free_channel(cinfo, data, id);
+	return 0;
+}
+
+static int virtio_get_max_msg(bool tx, struct scmi_chan_info *base_cinfo,
+			      int *max_msg)
+{
+	struct scmi_vio_channel *vioch = base_cinfo->transport_info;
+
+	*max_msg = virtqueue_get_vring_size(vioch->vqueue);
+
+	/* Tx messages need multiple descriptors. */
+	if (tx)
+		*max_msg /= DESCR_PER_TX_MSG;
+
+	if (*max_msg > MSG_TOKEN_MAX) {
+		dev_notice(
+			base_cinfo->dev,
+			"Only %ld messages can be pending simultaneously, while the virtqueue could hold %d\n",
+			MSG_TOKEN_MAX, *max_msg);
+		*max_msg = MSG_TOKEN_MAX;
+	}
+
+	return 0;
+}
+
+static int virtio_xfer_init_buffers(struct scmi_chan_info *cinfo,
+				    struct scmi_xfer *xfer, int max_msg_size)
+{
+	struct scmi_vio_channel *vioch = cinfo->transport_info;
+	struct scmi_vio_msg *msg;
+
+	msg = devm_kzalloc(cinfo->dev, sizeof(*msg), GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	xfer->extra_data = msg;
+
+	if (vioch->is_rx) {
+		int rc;
+		unsigned long iflags;
+
+		msg->input = devm_kzalloc(cinfo->dev,
+					  sizeof(*msg->input) + max_msg_size,
+					  GFP_KERNEL);
+		if (!msg->input)
+			return -ENOMEM;
+
+		/*
+		 * xfer->rx.buf will be set to notification or delayed response
+		 * specific values in the receive callback, according to the
+		 * type of the received message.
+		 */
+
+		spin_lock_irqsave(&vioch->lock, iflags);
+		rc = scmi_vio_populate_vq_rx(vioch, xfer);
+		spin_unlock_irqrestore(&vioch->lock, iflags);
+		if (rc)
+			return rc;
+	} else {
+		msg->request =
+			devm_kzalloc(cinfo->dev,
+				     sizeof(*msg->request) + max_msg_size,
+				     GFP_KERNEL);
+		if (!msg->request)
+			return -ENOMEM;
+
+		xfer->tx.buf = msg->request->data;
+
+		msg->input = devm_kzalloc(
+			cinfo->dev, sizeof(msg->input->response) + max_msg_size,
+			GFP_KERNEL);
+		if (!msg->input)
+			return -ENOMEM;
+
+		xfer->rx.buf = msg->input->response.data;
+	}
+
+	return 0;
+}
+
+static int scmi_vio_send(struct scmi_vio_channel *vioch, struct scmi_xfer *xfer)
+{
+	struct scatterlist sg_out;
+	struct scatterlist sg_in;
+	struct scatterlist *sgs[DESCR_PER_TX_MSG] = { &sg_out, &sg_in };
+	struct scmi_vio_msg *msg = xfer->extra_data;
+	unsigned long iflags;
+	int rc;
+
+	msg->completed = false;
+
+	sg_init_one(&sg_out, msg->request,
+		    sizeof(*msg->request) + xfer->tx.len);
+	sg_init_one(&sg_in, &msg->input->response,
+		    sizeof(msg->input->response) + xfer->rx.len);
+
+	spin_lock_irqsave(&vioch->lock, iflags);
+	rc = virtqueue_add_sgs(vioch->vqueue, sgs, 1, 1, xfer, GFP_ATOMIC);
+	if (rc)
+		dev_err(vioch->cinfo->dev, "%s() rc=%d\n", __func__, rc);
+	else
+		virtqueue_kick(vioch->vqueue);
+	spin_unlock_irqrestore(&vioch->lock, iflags);
+
+	return rc;
+}
+
+static int virtio_send_message(struct scmi_chan_info *cinfo,
+			       struct scmi_xfer *xfer)
+{
+	uint32_t hdr;
+	struct scmi_vio_channel *vioch = cinfo->transport_info;
+	struct virtio_device *vdev = vioch->vqueue->vdev;
+	struct scmi_vio_msg *msg = xfer->extra_data;
+
+	hdr = pack_scmi_header(&xfer->hdr);
+
+	msg->request->hdr = cpu_to_virtio32(vdev, hdr);
+
+	return scmi_vio_send(vioch, xfer);
+}
+
+static void virtio_fetch_response(struct scmi_chan_info *cinfo,
+				  struct scmi_xfer *xfer)
+{
+	struct scmi_vio_channel *vioch = cinfo->transport_info;
+	struct scmi_vio_msg *msg = xfer->extra_data;
+
+	xfer->hdr.status = virtio32_to_cpu(vioch->vqueue->vdev,
+					   msg->input->response.status);
+}
+
+static void dummy_fetch_notification(struct scmi_chan_info *cinfo,
+				     size_t max_len, struct scmi_xfer *xfer)
+{
+	(void)cinfo;
+	(void)max_len;
+	(void)xfer;
+}
+
+static void dummy_clear_channel(struct scmi_chan_info *cinfo)
+{
+	(void)cinfo;
+}
+
+static bool virtio_poll_done(struct scmi_chan_info *cinfo,
+			     struct scmi_xfer *xfer)
+{
+	struct scmi_vio_channel *vioch = cinfo->transport_info;
+	struct scmi_vio_msg *msg = xfer->extra_data;
+	unsigned long iflags;
+	bool completed;
+
+	spin_lock_irqsave(&vioch->lock, iflags);
+	completed = msg->completed;
+	spin_unlock_irqrestore(&vioch->lock, iflags);
+
+	return completed;
+}
+
+static const struct scmi_transport_ops scmi_virtio_ops = {
+	.link_supplier = virtio_link_supplier,
+	.chan_available = virtio_chan_available,
+	.chan_setup = virtio_chan_setup,
+	.chan_free = virtio_chan_free,
+	.get_max_msg = virtio_get_max_msg,
+	.send_message = virtio_send_message,
+	.fetch_response = virtio_fetch_response,
+	.fetch_notification = dummy_fetch_notification,
+	.clear_channel = dummy_clear_channel,
+	.poll_done = virtio_poll_done,
+	.xfer_init_buffers = virtio_xfer_init_buffers,
+};
+
+const struct scmi_desc scmi_virtio_desc = {
+	.ops = &scmi_virtio_ops,
+	.max_rx_timeout_ms = 60000, /* for non-realtime virtio devices */
+	.max_msg = 0, /* overridden by virtio_get_max_msg() */
+	.max_msg_size = VIRTIO_SCMI_MAX_MSG_SIZE,
+};
+
+static int scmi_vio_probe(struct virtio_device *vdev)
+{
+	struct device *dev = &vdev->dev;
+	struct scmi_vio_channel **vioch;
+	bool have_vq_rx;
+	int vq_cnt;
+	int i;
+	struct virtqueue *vqs[VIRTIO_SCMI_VQ_MAX_CNT];
+
+	vioch = devm_kcalloc(dev, VIRTIO_SCMI_VQ_MAX_CNT, sizeof(*vioch),
+			     GFP_KERNEL);
+	if (!vioch)
+		return -ENOMEM;
+
+	have_vq_rx = virtio_has_feature(vdev, VIRTIO_SCMI_F_P2A_CHANNELS);
+	vq_cnt = have_vq_rx ? VIRTIO_SCMI_VQ_MAX_CNT : 1;
+
+	for (i = 0; i < vq_cnt; i++) {
+		vioch[i] = devm_kzalloc(dev, sizeof(**vioch), GFP_KERNEL);
+		if (!vioch[i])
+			return -ENOMEM;
+	}
+
+	if (have_vq_rx)
+		vioch[VIRTIO_SCMI_VQ_RX]->is_rx = true;
+
+	if (virtio_find_vqs(vdev, vq_cnt, vqs, scmi_vio_complete_callbacks,
+			    scmi_vio_vqueue_names, NULL)) {
+		dev_err(dev, "Failed to get %d virtqueue(s)\n", vq_cnt);
+		return -1;
+	}
+	dev_info(dev, "Found %d virtqueue(s)\n", vq_cnt);
+
+	for (i = 0; i < vq_cnt; i++) {
+		spin_lock_init(&vioch[i]->lock);
+		vioch[i]->vqueue = vqs[i];
+		vioch[i]->vqueue->priv = vioch[i];
+	}
+
+	vdev->priv = vioch;
+
+	virtio_device_ready(vdev);
+
+	return 0;
+}
+
+static unsigned int features[] = {
+	VIRTIO_SCMI_F_P2A_CHANNELS,
+};
+
+static const struct virtio_device_id id_table[] = {
+	{ VIRTIO_ID_SCMI, VIRTIO_DEV_ANY_ID },
+	{ 0 }
+};
+
+static struct virtio_driver virtio_scmi_driver = {
+	.driver.name = "scmi-virtio",
+	.driver.owner = THIS_MODULE,
+	.feature_table = features,
+	.feature_table_size = ARRAY_SIZE(features),
+	.id_table = id_table,
+	.probe = scmi_vio_probe,
+};
+
+int __init virtio_scmi_init(void)
+{
+	return register_virtio_driver(&virtio_scmi_driver);
+}
+
+void __exit virtio_scmi_exit(void)
+{
+	unregister_virtio_driver(&virtio_scmi_driver);
+}
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
index bc740d6d2259..287daba2abdb 100644
--- a/include/uapi/linux/virtio_ids.h
+++ b/include/uapi/linux/virtio_ids.h
@@ -49,5 +49,6 @@
 #define VIRTIO_ID_FS           26 /* virtio filesystem */
 #define VIRTIO_ID_PMEM         27 /* virtio pmem */
 #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
+#define VIRTIO_ID_SCMI         32 /* virtio SCMI */
 
 #endif /* _LINUX_VIRTIO_IDS_H */
diff --git a/include/uapi/linux/virtio_scmi.h b/include/uapi/linux/virtio_scmi.h
new file mode 100644
index 000000000000..9f21b3dbbfe2
--- /dev/null
+++ b/include/uapi/linux/virtio_scmi.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Copyright (C) 2020 OpenSynergy GmbH
+ */
+
+#ifndef _UAPI_LINUX_VIRTIO_SCMI_H
+#define _UAPI_LINUX_VIRTIO_SCMI_H
+
+#include <linux/virtio_types.h>
+
+/* Feature bits */
+
+/* Device implements some SCMI notifications, or delayed responses. */
+#define VIRTIO_SCMI_F_P2A_CHANNELS 0
+
+/* Device implements any SCMI statistics shared memory region */
+#define VIRTIO_SCMI_F_SHARED_MEMORY 1
+
+/* Virtqueues */
+
+#define VIRTIO_SCMI_VQ_TX 0 /* cmdq */
+#define VIRTIO_SCMI_VQ_RX 1 /* eventq */
+#define VIRTIO_SCMI_VQ_MAX_CNT 2
+
+struct virtio_scmi_request {
+	__virtio32 hdr;
+	__u8 data[];
+};
+
+struct virtio_scmi_response {
+	__virtio32 hdr;
+	__virtio32 status;
+	__u8 data[];
+};
+
+struct virtio_scmi_notification {
+	__virtio32 hdr;
+	__u8 data[];
+};
+
+#endif /* _UAPI_LINUX_VIRTIO_SCMI_H */