xref: /kvmtool/virtio/console.c (revision e59b1ed04e204bb13067bb379820e6b259246f38)
1416b2c2dSAsias He #include "kvm/virtio-console.h"
231638bcaSCyrill Gorcunov #include "kvm/virtio-pci-dev.h"
39bd8ac5cSAsias He #include "kvm/disk-image.h"
439d6af07SAsias He #include "kvm/virtio.h"
59bd8ac5cSAsias He #include "kvm/ioport.h"
69bd8ac5cSAsias He #include "kvm/util.h"
79bd8ac5cSAsias He #include "kvm/term.h"
87512639bSAsias He #include "kvm/mutex.h"
99bd8ac5cSAsias He #include "kvm/kvm.h"
109bd8ac5cSAsias He #include "kvm/pci.h"
11b6638c22SSasha Levin #include "kvm/threadpool.h"
122449f6e3SSasha Levin #include "kvm/irq.h"
13*e59b1ed0SSasha Levin #include "kvm/guest_compat.h"
149bd8ac5cSAsias He 
159bd8ac5cSAsias He #include <linux/virtio_console.h>
169bd8ac5cSAsias He #include <linux/virtio_ring.h>
179bd8ac5cSAsias He #include <linux/virtio_blk.h>
189bd8ac5cSAsias He 
199bd8ac5cSAsias He #include <sys/uio.h>
209bd8ac5cSAsias He #include <sys/types.h>
219bd8ac5cSAsias He #include <sys/stat.h>
229bd8ac5cSAsias He #include <termios.h>
239bd8ac5cSAsias He #include <assert.h>
249bd8ac5cSAsias He #include <unistd.h>
259bd8ac5cSAsias He #include <fcntl.h>
269bd8ac5cSAsias He 
279bd8ac5cSAsias He #define VIRTIO_CONSOLE_QUEUE_SIZE	128
289bd8ac5cSAsias He #define VIRTIO_CONSOLE_NUM_QUEUES	2
299bd8ac5cSAsias He #define VIRTIO_CONSOLE_RX_QUEUE		0
309bd8ac5cSAsias He #define VIRTIO_CONSOLE_TX_QUEUE		1
319bd8ac5cSAsias He 
322449f6e3SSasha Levin static struct pci_device_header virtio_console_pci_device = {
332449f6e3SSasha Levin 	.vendor_id		= PCI_VENDOR_ID_REDHAT_QUMRANET,
342449f6e3SSasha Levin 	.device_id		= PCI_DEVICE_ID_VIRTIO_CONSOLE,
352449f6e3SSasha Levin 	.header_type		= PCI_HEADER_TYPE_NORMAL,
362449f6e3SSasha Levin 	.revision_id		= 0,
372449f6e3SSasha Levin 	.class			= 0x078000,
382449f6e3SSasha Levin 	.subsys_vendor_id	= PCI_SUBSYSTEM_VENDOR_ID_REDHAT_QUMRANET,
390a7ab0c6SSasha Levin 	.subsys_id		= VIRTIO_ID_CONSOLE,
402449f6e3SSasha Levin };
412449f6e3SSasha Levin 
42a643306eSSasha Levin struct con_dev {
437512639bSAsias He 	pthread_mutex_t			mutex;
447512639bSAsias He 
459bd8ac5cSAsias He 	struct virt_queue		vqs[VIRTIO_CONSOLE_NUM_QUEUES];
469bd8ac5cSAsias He 	struct virtio_console_config	console_config;
473fdf659dSSasha Levin 	u32				host_features;
483fdf659dSSasha Levin 	u32				guest_features;
493fdf659dSSasha Levin 	u16				config_vector;
503fdf659dSSasha Levin 	u8				status;
510ba84a57SAsias He 	u8				isr;
523fdf659dSSasha Levin 	u16				queue_selector;
534b385ac4SSasha Levin 	u16				base_addr;
54*e59b1ed0SSasha Levin 	int				compat_id;
55b6638c22SSasha Levin 
56df0c7f57SSasha Levin 	struct thread_pool__job		jobs[VIRTIO_CONSOLE_NUM_QUEUES];
579bd8ac5cSAsias He };
589bd8ac5cSAsias He 
59a643306eSSasha Levin static struct con_dev cdev = {
607512639bSAsias He 	.mutex				= PTHREAD_MUTEX_INITIALIZER,
617512639bSAsias He 
629bd8ac5cSAsias He 	.console_config = {
639bd8ac5cSAsias He 		.cols			= 80,
649bd8ac5cSAsias He 		.rows			= 24,
659bd8ac5cSAsias He 		.max_nr_ports		= 1,
669bd8ac5cSAsias He 	},
679bd8ac5cSAsias He 
689bd8ac5cSAsias He 	.host_features			= 0,
699bd8ac5cSAsias He };
709bd8ac5cSAsias He 
719bd8ac5cSAsias He /*
729bd8ac5cSAsias He  * Interrupts are injected for hvc0 only.
739bd8ac5cSAsias He  */
7443835ac9SSasha Levin static void virtio_console__inject_interrupt_callback(struct kvm *kvm, void *param)
759bd8ac5cSAsias He {
769bd8ac5cSAsias He 	struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE];
779bd8ac5cSAsias He 	struct virt_queue *vq;
783fdf659dSSasha Levin 	u16 out, in;
793fdf659dSSasha Levin 	u16 head;
809bd8ac5cSAsias He 	int len;
819bd8ac5cSAsias He 
82a643306eSSasha Levin 	mutex_lock(&cdev.mutex);
837512639bSAsias He 
84b6638c22SSasha Levin 	vq = param;
859bd8ac5cSAsias He 
869bd8ac5cSAsias He 	if (term_readable(CONSOLE_VIRTIO) && virt_queue__available(vq)) {
8743835ac9SSasha Levin 		head = virt_queue__get_iov(vq, iov, &out, &in, kvm);
889bd8ac5cSAsias He 		len = term_getc_iov(CONSOLE_VIRTIO, iov, in);
899bd8ac5cSAsias He 		virt_queue__set_used_elem(vq, head, len);
9043835ac9SSasha Levin 		virt_queue__trigger_irq(vq, virtio_console_pci_device.irq_line, &cdev.isr, kvm);
919bd8ac5cSAsias He 	}
927512639bSAsias He 
93a643306eSSasha Levin 	mutex_unlock(&cdev.mutex);
949bd8ac5cSAsias He }
959bd8ac5cSAsias He 
9643835ac9SSasha Levin void virtio_console__inject_interrupt(struct kvm *kvm)
97b6638c22SSasha Levin {
98df0c7f57SSasha Levin 	thread_pool__do_job(&cdev.jobs[VIRTIO_CONSOLE_RX_QUEUE]);
99b6638c22SSasha Levin }
100b6638c22SSasha Levin 
101c9f6a037SXiao Guangrong static bool virtio_console_pci_io_device_specific_in(void *data, unsigned long offset, int size)
1029bd8ac5cSAsias He {
103a643306eSSasha Levin 	u8 *config_space = (u8 *) &cdev.console_config;
1049bd8ac5cSAsias He 
105c9f6a037SXiao Guangrong 	if (size != 1)
1069bd8ac5cSAsias He 		return false;
1079bd8ac5cSAsias He 
108b8f43678SSasha Levin 	if ((offset - VIRTIO_MSI_CONFIG_VECTOR) > sizeof(struct virtio_console_config))
1094542f276SCyrill Gorcunov 		pr_error("config offset is too big: %li", offset - VIRTIO_MSI_CONFIG_VECTOR);
1109bd8ac5cSAsias He 
111b8f43678SSasha Levin 	ioport__write8(data, config_space[offset - VIRTIO_MSI_CONFIG_VECTOR]);
1129bd8ac5cSAsias He 
1139bd8ac5cSAsias He 	return true;
1149bd8ac5cSAsias He }
1159bd8ac5cSAsias He 
116c9f6a037SXiao Guangrong static bool virtio_console_pci_io_in(struct ioport *ioport, struct kvm *kvm, u16 port, void *data, int size)
1179bd8ac5cSAsias He {
1184b385ac4SSasha Levin 	unsigned long offset = port - cdev.base_addr;
1197512639bSAsias He 	bool ret = true;
1207512639bSAsias He 
121a643306eSSasha Levin 	mutex_lock(&cdev.mutex);
1229bd8ac5cSAsias He 
1239bd8ac5cSAsias He 	switch (offset) {
1249bd8ac5cSAsias He 	case VIRTIO_PCI_HOST_FEATURES:
125a643306eSSasha Levin 		ioport__write32(data, cdev.host_features);
1269bd8ac5cSAsias He 		break;
1279bd8ac5cSAsias He 	case VIRTIO_PCI_GUEST_FEATURES:
1287512639bSAsias He 		ret = false;
1297512639bSAsias He 		break;
1309bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_PFN:
131a643306eSSasha Levin 		ioport__write32(data, cdev.vqs[cdev.queue_selector].pfn);
1329bd8ac5cSAsias He 		break;
1339bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_NUM:
1349bd8ac5cSAsias He 		ioport__write16(data, VIRTIO_CONSOLE_QUEUE_SIZE);
1359bd8ac5cSAsias He 		break;
1369bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_SEL:
1379bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_NOTIFY:
1387512639bSAsias He 		ret = false;
1397512639bSAsias He 		break;
1409bd8ac5cSAsias He 	case VIRTIO_PCI_STATUS:
141a643306eSSasha Levin 		ioport__write8(data, cdev.status);
1429bd8ac5cSAsias He 		break;
1439bd8ac5cSAsias He 	case VIRTIO_PCI_ISR:
1440ba84a57SAsias He 		ioport__write8(data, cdev.isr);
14543835ac9SSasha Levin 		kvm__irq_line(kvm, virtio_console_pci_device.irq_line, VIRTIO_IRQ_LOW);
1460ba84a57SAsias He 		cdev.isr = VIRTIO_IRQ_LOW;
1479bd8ac5cSAsias He 		break;
1489bd8ac5cSAsias He 	case VIRTIO_MSI_CONFIG_VECTOR:
149a643306eSSasha Levin 		ioport__write16(data, cdev.config_vector);
1509bd8ac5cSAsias He 		break;
1519bd8ac5cSAsias He 	default:
152c9f6a037SXiao Guangrong 		ret = virtio_console_pci_io_device_specific_in(data, offset, size);
1539bd8ac5cSAsias He 	};
1549bd8ac5cSAsias He 
155a643306eSSasha Levin 	mutex_unlock(&cdev.mutex);
1567512639bSAsias He 
1577512639bSAsias He 	return ret;
1589bd8ac5cSAsias He }
1599bd8ac5cSAsias He 
16043835ac9SSasha Levin static void virtio_console_handle_callback(struct kvm *kvm, void *param)
1619bd8ac5cSAsias He {
1629bd8ac5cSAsias He 	struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE];
1639bd8ac5cSAsias He 	struct virt_queue *vq;
1643fdf659dSSasha Levin 	u16 out, in;
1653fdf659dSSasha Levin 	u16 head;
1663fdf659dSSasha Levin 	u32 len;
1679bd8ac5cSAsias He 
168b6638c22SSasha Levin 	vq = param;
1699bd8ac5cSAsias He 
17020f0438aSAsias He 	/*
17120f0438aSAsias He 	 * The current Linux implementation polls for the buffer
17220f0438aSAsias He 	 * to be used, rather than waiting for an interrupt.
17320f0438aSAsias He 	 * So there is no need to inject an interrupt for the tx path.
17420f0438aSAsias He 	 */
17520f0438aSAsias He 
1769bd8ac5cSAsias He 	while (virt_queue__available(vq)) {
17743835ac9SSasha Levin 		head = virt_queue__get_iov(vq, iov, &out, &in, kvm);
1789bd8ac5cSAsias He 		len = term_putc_iov(CONSOLE_VIRTIO, iov, out);
1799bd8ac5cSAsias He 		virt_queue__set_used_elem(vq, head, len);
1809bd8ac5cSAsias He 	}
1819bd8ac5cSAsias He 
1829bd8ac5cSAsias He }
1839bd8ac5cSAsias He 
184c9f6a037SXiao Guangrong static bool virtio_console_pci_io_out(struct ioport *ioport, struct kvm *kvm, u16 port, void *data, int size)
1859bd8ac5cSAsias He {
1864b385ac4SSasha Levin 	unsigned long offset = port - cdev.base_addr;
1877512639bSAsias He 	bool ret = true;
1889bd8ac5cSAsias He 
189a643306eSSasha Levin 	mutex_lock(&cdev.mutex);
190e147d838SAsias He 
1919bd8ac5cSAsias He 	switch (offset) {
1929bd8ac5cSAsias He 	case VIRTIO_PCI_GUEST_FEATURES:
193a643306eSSasha Levin 		cdev.guest_features	= ioport__read32(data);
1949bd8ac5cSAsias He 		break;
1959bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_PFN: {
1969bd8ac5cSAsias He 		struct virt_queue *queue;
1979bd8ac5cSAsias He 		void *p;
1989bd8ac5cSAsias He 
199a643306eSSasha Levin 		assert(cdev.queue_selector < VIRTIO_CONSOLE_NUM_QUEUES);
2009bd8ac5cSAsias He 
201*e59b1ed0SSasha Levin 		compat__remove_message(cdev.compat_id);
202*e59b1ed0SSasha Levin 
203a643306eSSasha Levin 		queue			= &cdev.vqs[cdev.queue_selector];
2049bd8ac5cSAsias He 		queue->pfn		= ioport__read32(data);
20543835ac9SSasha Levin 		p			= guest_pfn_to_host(kvm, queue->pfn);
2069bd8ac5cSAsias He 
207b8f43678SSasha Levin 		vring_init(&queue->vring, VIRTIO_CONSOLE_QUEUE_SIZE, p, VIRTIO_PCI_VRING_ALIGN);
2089bd8ac5cSAsias He 
209a643306eSSasha Levin 		if (cdev.queue_selector == VIRTIO_CONSOLE_TX_QUEUE)
210df0c7f57SSasha Levin 			thread_pool__init_job(&cdev.jobs[cdev.queue_selector], kvm, virtio_console_handle_callback, queue);
211a643306eSSasha Levin 		else if (cdev.queue_selector == VIRTIO_CONSOLE_RX_QUEUE)
212df0c7f57SSasha Levin 			thread_pool__init_job(&cdev.jobs[cdev.queue_selector], kvm, virtio_console__inject_interrupt_callback, queue);
213b6638c22SSasha Levin 
2149bd8ac5cSAsias He 		break;
2159bd8ac5cSAsias He 	}
2169bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_SEL:
217a643306eSSasha Levin 		cdev.queue_selector	= ioport__read16(data);
2189bd8ac5cSAsias He 		break;
2199bd8ac5cSAsias He 	case VIRTIO_PCI_QUEUE_NOTIFY: {
220a643306eSSasha Levin 		u16 queue_index		= ioport__read16(data);
221df0c7f57SSasha Levin 		thread_pool__do_job(&cdev.jobs[queue_index]);
2229bd8ac5cSAsias He 		break;
2239bd8ac5cSAsias He 	}
2249bd8ac5cSAsias He 	case VIRTIO_PCI_STATUS:
225a643306eSSasha Levin 		cdev.status		= ioport__read8(data);
2269bd8ac5cSAsias He 		break;
2279bd8ac5cSAsias He 	case VIRTIO_MSI_CONFIG_VECTOR:
228a643306eSSasha Levin 		cdev.config_vector	= VIRTIO_MSI_NO_VECTOR;
2299bd8ac5cSAsias He 		break;
2309bd8ac5cSAsias He 	case VIRTIO_MSI_QUEUE_VECTOR:
2319bd8ac5cSAsias He 		break;
2329bd8ac5cSAsias He 	default:
2337512639bSAsias He 		ret			= false;
234407475bfSPekka Enberg 		break;
2359bd8ac5cSAsias He 	};
2369bd8ac5cSAsias He 
237a643306eSSasha Levin 	mutex_unlock(&cdev.mutex);
238407475bfSPekka Enberg 
2397512639bSAsias He 	return ret;
2409bd8ac5cSAsias He }
2419bd8ac5cSAsias He 
2429bd8ac5cSAsias He static struct ioport_operations virtio_console_io_ops = {
2439bd8ac5cSAsias He 	.io_in			= virtio_console_pci_io_in,
2449bd8ac5cSAsias He 	.io_out			= virtio_console_pci_io_out,
2459bd8ac5cSAsias He };
2469bd8ac5cSAsias He 
24743835ac9SSasha Levin void virtio_console__init(struct kvm *kvm)
2489bd8ac5cSAsias He {
2492449f6e3SSasha Levin 	u8 dev, line, pin;
2504b385ac4SSasha Levin 	u16 console_base_addr;
2512449f6e3SSasha Levin 
2520a7ab0c6SSasha Levin 	if (irq__register_device(VIRTIO_ID_CONSOLE, &dev, &pin, &line) < 0)
2532449f6e3SSasha Levin 		return;
2542449f6e3SSasha Levin 
2552449f6e3SSasha Levin 	virtio_console_pci_device.irq_pin	= pin;
2562449f6e3SSasha Levin 	virtio_console_pci_device.irq_line	= line;
2574b385ac4SSasha Levin 	console_base_addr			= ioport__register(IOPORT_EMPTY, &virtio_console_io_ops, IOPORT_SIZE, NULL);
2584b385ac4SSasha Levin 	virtio_console_pci_device.bar[0]	= console_base_addr | PCI_BASE_ADDRESS_SPACE_IO;
2594b385ac4SSasha Levin 	cdev.base_addr				= console_base_addr;
2602449f6e3SSasha Levin 	pci__register(&virtio_console_pci_device, dev);
261*e59b1ed0SSasha Levin 
262*e59b1ed0SSasha Levin 	cdev.compat_id = compat__add_message("virtio-console device was not detected",
263*e59b1ed0SSasha Levin 						"While you have requested a virtio-console device, "
264*e59b1ed0SSasha Levin 						"the guest kernel didn't seem to detect it.\n"
265*e59b1ed0SSasha Levin 						"Please make sure that the kernel was compiled"
266*e59b1ed0SSasha Levin 						"with CONFIG_VIRTIO_CONSOLE.");
2679bd8ac5cSAsias He }
268