| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <drm/drm_fb_helper.h> |
| |
| #include "bochs.h" |
| |
| static bool enable_fbdev = true; |
| module_param_named(fbdev, enable_fbdev, bool, 0444); |
| MODULE_PARM_DESC(fbdev, "register fbdev device"); |
| |
| /* ---------------------------------------------------------------------- */ |
| /* drm interface */ |
| |
| static int bochs_unload(struct drm_device *dev) |
| { |
| struct bochs_device *bochs = dev->dev_private; |
| |
| bochs_fbdev_fini(bochs); |
| bochs_kms_fini(bochs); |
| bochs_mm_fini(bochs); |
| bochs_hw_fini(dev); |
| kfree(bochs); |
| dev->dev_private = NULL; |
| return 0; |
| } |
| |
| static int bochs_load(struct drm_device *dev, unsigned long flags) |
| { |
| struct bochs_device *bochs; |
| int ret; |
| |
| bochs = kzalloc(sizeof(*bochs), GFP_KERNEL); |
| if (bochs == NULL) |
| return -ENOMEM; |
| dev->dev_private = bochs; |
| bochs->dev = dev; |
| |
| ret = bochs_hw_init(dev, flags); |
| if (ret) |
| goto err; |
| |
| ret = bochs_mm_init(bochs); |
| if (ret) |
| goto err; |
| |
| ret = bochs_kms_init(bochs); |
| if (ret) |
| goto err; |
| |
| if (enable_fbdev) |
| bochs_fbdev_init(bochs); |
| |
| return 0; |
| |
| err: |
| bochs_unload(dev); |
| return ret; |
| } |
| |
| static const struct file_operations bochs_fops = { |
| .owner = THIS_MODULE, |
| .open = drm_open, |
| .release = drm_release, |
| .unlocked_ioctl = drm_ioctl, |
| .compat_ioctl = drm_compat_ioctl, |
| .poll = drm_poll, |
| .read = drm_read, |
| .llseek = no_llseek, |
| .mmap = bochs_mmap, |
| }; |
| |
| static struct drm_driver bochs_driver = { |
| .driver_features = DRIVER_GEM | DRIVER_MODESET, |
| .load = bochs_load, |
| .unload = bochs_unload, |
| .set_busid = drm_pci_set_busid, |
| .fops = &bochs_fops, |
| .name = "bochs-drm", |
| .desc = "bochs dispi vga interface (qemu stdvga)", |
| .date = "20130925", |
| .major = 1, |
| .minor = 0, |
| .gem_free_object_unlocked = bochs_gem_free_object, |
| .dumb_create = bochs_dumb_create, |
| .dumb_map_offset = bochs_dumb_mmap_offset, |
| .dumb_destroy = drm_gem_dumb_destroy, |
| }; |
| |
| /* ---------------------------------------------------------------------- */ |
| /* pm interface */ |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int bochs_pm_suspend(struct device *dev) |
| { |
| struct pci_dev *pdev = to_pci_dev(dev); |
| struct drm_device *drm_dev = pci_get_drvdata(pdev); |
| struct bochs_device *bochs = drm_dev->dev_private; |
| |
| drm_kms_helper_poll_disable(drm_dev); |
| |
| if (bochs->fb.initialized) { |
| console_lock(); |
| drm_fb_helper_set_suspend(&bochs->fb.helper, 1); |
| console_unlock(); |
| } |
| |
| return 0; |
| } |
| |
| static int bochs_pm_resume(struct device *dev) |
| { |
| struct pci_dev *pdev = to_pci_dev(dev); |
| struct drm_device *drm_dev = pci_get_drvdata(pdev); |
| struct bochs_device *bochs = drm_dev->dev_private; |
| |
| drm_helper_resume_force_mode(drm_dev); |
| |
| if (bochs->fb.initialized) { |
| console_lock(); |
| drm_fb_helper_set_suspend(&bochs->fb.helper, 0); |
| console_unlock(); |
| } |
| |
| drm_kms_helper_poll_enable(drm_dev); |
| return 0; |
| } |
| #endif |
| |
| static const struct dev_pm_ops bochs_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(bochs_pm_suspend, |
| bochs_pm_resume) |
| }; |
| |
| /* ---------------------------------------------------------------------- */ |
| /* pci interface */ |
| |
| static int bochs_kick_out_firmware_fb(struct pci_dev *pdev) |
| { |
| struct apertures_struct *ap; |
| |
| ap = alloc_apertures(1); |
| if (!ap) |
| return -ENOMEM; |
| |
| ap->ranges[0].base = pci_resource_start(pdev, 0); |
| ap->ranges[0].size = pci_resource_len(pdev, 0); |
| drm_fb_helper_remove_conflicting_framebuffers(ap, "bochsdrmfb", false); |
| kfree(ap); |
| |
| return 0; |
| } |
| |
| static int bochs_pci_probe(struct pci_dev *pdev, |
| const struct pci_device_id *ent) |
| { |
| unsigned long fbsize; |
| int ret; |
| |
| fbsize = pci_resource_len(pdev, 0); |
| if (fbsize < 4 * 1024 * 1024) { |
| DRM_ERROR("less than 4 MB video memory, ignoring device\n"); |
| return -ENOMEM; |
| } |
| |
| ret = bochs_kick_out_firmware_fb(pdev); |
| if (ret) |
| return ret; |
| |
| return drm_get_pci_dev(pdev, ent, &bochs_driver); |
| } |
| |
| static void bochs_pci_remove(struct pci_dev *pdev) |
| { |
| struct drm_device *dev = pci_get_drvdata(pdev); |
| |
| drm_put_dev(dev); |
| } |
| |
| static const struct pci_device_id bochs_pci_tbl[] = { |
| { |
| .vendor = 0x1234, |
| .device = 0x1111, |
| .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, |
| .subdevice = PCI_SUBDEVICE_ID_QEMU, |
| .driver_data = BOCHS_QEMU_STDVGA, |
| }, |
| { |
| .vendor = 0x1234, |
| .device = 0x1111, |
| .subvendor = PCI_ANY_ID, |
| .subdevice = PCI_ANY_ID, |
| .driver_data = BOCHS_UNKNOWN, |
| }, |
| { /* end of list */ } |
| }; |
| |
| static struct pci_driver bochs_pci_driver = { |
| .name = "bochs-drm", |
| .id_table = bochs_pci_tbl, |
| .probe = bochs_pci_probe, |
| .remove = bochs_pci_remove, |
| .driver.pm = &bochs_pm_ops, |
| }; |
| |
| /* ---------------------------------------------------------------------- */ |
| /* module init/exit */ |
| |
| static int __init bochs_init(void) |
| { |
| return drm_pci_init(&bochs_driver, &bochs_pci_driver); |
| } |
| |
| static void __exit bochs_exit(void) |
| { |
| drm_pci_exit(&bochs_driver, &bochs_pci_driver); |
| } |
| |
| module_init(bochs_init); |
| module_exit(bochs_exit); |
| |
| MODULE_DEVICE_TABLE(pci, bochs_pci_tbl); |
| MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>"); |
| MODULE_LICENSE("GPL"); |