162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2021 Microsoft
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/efi.h>
762306a36Sopenharmony_ci#include <linux/hyperv.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/pci.h>
1062306a36Sopenharmony_ci#include <linux/screen_info.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <drm/drm_aperture.h>
1362306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
1462306a36Sopenharmony_ci#include <drm/drm_drv.h>
1562306a36Sopenharmony_ci#include <drm/drm_fbdev_generic.h>
1662306a36Sopenharmony_ci#include <drm/drm_gem_shmem_helper.h>
1762306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "hyperv_drm.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define DRIVER_NAME "hyperv_drm"
2262306a36Sopenharmony_ci#define DRIVER_DESC "DRM driver for Hyper-V synthetic video device"
2362306a36Sopenharmony_ci#define DRIVER_DATE "2020"
2462306a36Sopenharmony_ci#define DRIVER_MAJOR 1
2562306a36Sopenharmony_ci#define DRIVER_MINOR 0
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ciDEFINE_DRM_GEM_FOPS(hv_fops);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic struct drm_driver hyperv_driver = {
3062306a36Sopenharmony_ci	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	.name		 = DRIVER_NAME,
3362306a36Sopenharmony_ci	.desc		 = DRIVER_DESC,
3462306a36Sopenharmony_ci	.date		 = DRIVER_DATE,
3562306a36Sopenharmony_ci	.major		 = DRIVER_MAJOR,
3662306a36Sopenharmony_ci	.minor		 = DRIVER_MINOR,
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	.fops		 = &hv_fops,
3962306a36Sopenharmony_ci	DRM_GEM_SHMEM_DRIVER_OPS,
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int hyperv_pci_probe(struct pci_dev *pdev,
4362306a36Sopenharmony_ci			    const struct pci_device_id *ent)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	return 0;
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic void hyperv_pci_remove(struct pci_dev *pdev)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic const struct pci_device_id hyperv_pci_tbl[] = {
5362306a36Sopenharmony_ci	{
5462306a36Sopenharmony_ci		.vendor = PCI_VENDOR_ID_MICROSOFT,
5562306a36Sopenharmony_ci		.device = PCI_DEVICE_ID_HYPERV_VIDEO,
5662306a36Sopenharmony_ci	},
5762306a36Sopenharmony_ci	{ /* end of list */ }
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/*
6162306a36Sopenharmony_ci * PCI stub to support gen1 VM.
6262306a36Sopenharmony_ci */
6362306a36Sopenharmony_cistatic struct pci_driver hyperv_pci_driver = {
6462306a36Sopenharmony_ci	.name =		KBUILD_MODNAME,
6562306a36Sopenharmony_ci	.id_table =	hyperv_pci_tbl,
6662306a36Sopenharmony_ci	.probe =	hyperv_pci_probe,
6762306a36Sopenharmony_ci	.remove =	hyperv_pci_remove,
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int hyperv_setup_vram(struct hyperv_drm_device *hv,
7162306a36Sopenharmony_ci			     struct hv_device *hdev)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct drm_device *dev = &hv->dev;
7462306a36Sopenharmony_ci	int ret;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	drm_aperture_remove_conflicting_framebuffers(screen_info.lfb_base,
7762306a36Sopenharmony_ci						     screen_info.lfb_size,
7862306a36Sopenharmony_ci						     &hyperv_driver);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	hv->fb_size = (unsigned long)hv->mmio_megabytes * 1024 * 1024;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	ret = vmbus_allocate_mmio(&hv->mem, hdev, 0, -1, hv->fb_size, 0x100000,
8362306a36Sopenharmony_ci				  true);
8462306a36Sopenharmony_ci	if (ret) {
8562306a36Sopenharmony_ci		drm_err(dev, "Failed to allocate mmio\n");
8662306a36Sopenharmony_ci		return -ENOMEM;
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/*
9062306a36Sopenharmony_ci	 * Map the VRAM cacheable for performance. This is also required for VM
9162306a36Sopenharmony_ci	 * connect to display properly for ARM64 Linux VM, as the host also maps
9262306a36Sopenharmony_ci	 * the VRAM cacheable.
9362306a36Sopenharmony_ci	 */
9462306a36Sopenharmony_ci	hv->vram = ioremap_cache(hv->mem->start, hv->fb_size);
9562306a36Sopenharmony_ci	if (!hv->vram) {
9662306a36Sopenharmony_ci		drm_err(dev, "Failed to map vram\n");
9762306a36Sopenharmony_ci		ret = -ENOMEM;
9862306a36Sopenharmony_ci		goto error;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	hv->fb_base = hv->mem->start;
10262306a36Sopenharmony_ci	return 0;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cierror:
10562306a36Sopenharmony_ci	vmbus_free_mmio(hv->mem->start, hv->fb_size);
10662306a36Sopenharmony_ci	return ret;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic int hyperv_vmbus_probe(struct hv_device *hdev,
11062306a36Sopenharmony_ci			      const struct hv_vmbus_device_id *dev_id)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct hyperv_drm_device *hv;
11362306a36Sopenharmony_ci	struct drm_device *dev;
11462306a36Sopenharmony_ci	int ret;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	hv = devm_drm_dev_alloc(&hdev->device, &hyperv_driver,
11762306a36Sopenharmony_ci				struct hyperv_drm_device, dev);
11862306a36Sopenharmony_ci	if (IS_ERR(hv))
11962306a36Sopenharmony_ci		return PTR_ERR(hv);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	dev = &hv->dev;
12262306a36Sopenharmony_ci	init_completion(&hv->wait);
12362306a36Sopenharmony_ci	hv_set_drvdata(hdev, hv);
12462306a36Sopenharmony_ci	hv->hdev = hdev;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	ret = hyperv_connect_vsp(hdev);
12762306a36Sopenharmony_ci	if (ret) {
12862306a36Sopenharmony_ci		drm_err(dev, "Failed to connect to vmbus.\n");
12962306a36Sopenharmony_ci		goto err_hv_set_drv_data;
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	ret = hyperv_setup_vram(hv, hdev);
13362306a36Sopenharmony_ci	if (ret)
13462306a36Sopenharmony_ci		goto err_vmbus_close;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/*
13762306a36Sopenharmony_ci	 * Should be done only once during init and resume. Failing to update
13862306a36Sopenharmony_ci	 * vram location is not fatal. Device will update dirty area till
13962306a36Sopenharmony_ci	 * preferred resolution only.
14062306a36Sopenharmony_ci	 */
14162306a36Sopenharmony_ci	ret = hyperv_update_vram_location(hdev, hv->fb_base);
14262306a36Sopenharmony_ci	if (ret)
14362306a36Sopenharmony_ci		drm_warn(dev, "Failed to update vram location.\n");
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	ret = hyperv_mode_config_init(hv);
14662306a36Sopenharmony_ci	if (ret)
14762306a36Sopenharmony_ci		goto err_free_mmio;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	ret = drm_dev_register(dev, 0);
15062306a36Sopenharmony_ci	if (ret) {
15162306a36Sopenharmony_ci		drm_err(dev, "Failed to register drm driver.\n");
15262306a36Sopenharmony_ci		goto err_free_mmio;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	drm_fbdev_generic_setup(dev, 0);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	return 0;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cierr_free_mmio:
16062306a36Sopenharmony_ci	vmbus_free_mmio(hv->mem->start, hv->fb_size);
16162306a36Sopenharmony_cierr_vmbus_close:
16262306a36Sopenharmony_ci	vmbus_close(hdev->channel);
16362306a36Sopenharmony_cierr_hv_set_drv_data:
16462306a36Sopenharmony_ci	hv_set_drvdata(hdev, NULL);
16562306a36Sopenharmony_ci	return ret;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic void hyperv_vmbus_remove(struct hv_device *hdev)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct drm_device *dev = hv_get_drvdata(hdev);
17162306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(dev);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	drm_dev_unplug(dev);
17462306a36Sopenharmony_ci	drm_atomic_helper_shutdown(dev);
17562306a36Sopenharmony_ci	vmbus_close(hdev->channel);
17662306a36Sopenharmony_ci	hv_set_drvdata(hdev, NULL);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	vmbus_free_mmio(hv->mem->start, hv->fb_size);
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic int hyperv_vmbus_suspend(struct hv_device *hdev)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	struct drm_device *dev = hv_get_drvdata(hdev);
18462306a36Sopenharmony_ci	int ret;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	ret = drm_mode_config_helper_suspend(dev);
18762306a36Sopenharmony_ci	if (ret)
18862306a36Sopenharmony_ci		return ret;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	vmbus_close(hdev->channel);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return 0;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic int hyperv_vmbus_resume(struct hv_device *hdev)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	struct drm_device *dev = hv_get_drvdata(hdev);
19862306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(dev);
19962306a36Sopenharmony_ci	int ret;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	ret = hyperv_connect_vsp(hdev);
20262306a36Sopenharmony_ci	if (ret)
20362306a36Sopenharmony_ci		return ret;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	ret = hyperv_update_vram_location(hdev, hv->fb_base);
20662306a36Sopenharmony_ci	if (ret)
20762306a36Sopenharmony_ci		return ret;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return drm_mode_config_helper_resume(dev);
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic const struct hv_vmbus_device_id hyperv_vmbus_tbl[] = {
21362306a36Sopenharmony_ci	/* Synthetic Video Device GUID */
21462306a36Sopenharmony_ci	{HV_SYNTHVID_GUID},
21562306a36Sopenharmony_ci	{}
21662306a36Sopenharmony_ci};
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic struct hv_driver hyperv_hv_driver = {
21962306a36Sopenharmony_ci	.name = KBUILD_MODNAME,
22062306a36Sopenharmony_ci	.id_table = hyperv_vmbus_tbl,
22162306a36Sopenharmony_ci	.probe = hyperv_vmbus_probe,
22262306a36Sopenharmony_ci	.remove = hyperv_vmbus_remove,
22362306a36Sopenharmony_ci	.suspend = hyperv_vmbus_suspend,
22462306a36Sopenharmony_ci	.resume = hyperv_vmbus_resume,
22562306a36Sopenharmony_ci	.driver = {
22662306a36Sopenharmony_ci		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
22762306a36Sopenharmony_ci	},
22862306a36Sopenharmony_ci};
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int __init hyperv_init(void)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	int ret;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (drm_firmware_drivers_only())
23562306a36Sopenharmony_ci		return -ENODEV;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	ret = pci_register_driver(&hyperv_pci_driver);
23862306a36Sopenharmony_ci	if (ret != 0)
23962306a36Sopenharmony_ci		return ret;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	return vmbus_driver_register(&hyperv_hv_driver);
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic void __exit hyperv_exit(void)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	vmbus_driver_unregister(&hyperv_hv_driver);
24762306a36Sopenharmony_ci	pci_unregister_driver(&hyperv_pci_driver);
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cimodule_init(hyperv_init);
25162306a36Sopenharmony_cimodule_exit(hyperv_exit);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, hyperv_pci_tbl);
25462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(vmbus, hyperv_vmbus_tbl);
25562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
25662306a36Sopenharmony_ciMODULE_AUTHOR("Deepak Rawat <drawat.floss@gmail.com>");
25762306a36Sopenharmony_ciMODULE_DESCRIPTION("DRM driver for Hyper-V synthetic video device");
258