core: - split DSC helpers from DP helpers - clang build fixes for drm/mm test - drop simple pipeline support for gem vram - document submission error signaling - move drm_rect to drm core module from kms helper - add default client setup to most drivers - move to video aperture helpers instead of drm ones tests: - new framebuffer tests ttm: - remove swapped and pinned BOs from TTM lru panic: - fix uninit spinlock - add ABGR2101010 support bridge: - add TI TDP158 support - use standard PM OPS dma-fence: - use read_trylock instead of read_lock to help lockdep scheduler: - add errno to sched start to report different errors - add locking to drm_sched_entity_modify_sched - improve documentation xe: - add drm_line_printer - lots of refactoring - Enable Xe2 + PES disaggregation - add new ARL PCI ID - SRIOV development work - fix exec unnecessary implicit fence - define and parse OA sync props - forcewake refactoring i915: - Enable BMG/LNL ultra joiner - Enable 10bpx + CCS scanout on ICL+, fp16/CCS on TGL+ - use DSB for plane/color mgmt - Arrow lake PCI IDs - lots of i915/xe display refactoring - enable PXP GuC autoteardown - Pantherlake (PTL) Xe3 LPD display enablement - Allow fastset HDR infoframe changes - write DP source OUI for non-eDP sinks - share PCI IDs between i915 and xe amdgpu: - SDMA queue reset support - SMU 13.0.6, JPEG 4.0.3 updates - Initial runtime repartitioning support - rework IP structs for multiple IP instances - Fetch EDID from _DDC if available - SMU13 zero rpm user control - lots of fixes/cleanups amdkfd: - Increase event FIFO size - add topology cap flag for per queue reset msm: - DPU: - SA8775P support - (disabled by default) MSM8917, MSM8937, MSM8953 and MSM8996 support - Enable large framebuffer support - Drop MSM8998 and SDM845 - DP: - SA8775P support - GPU: - a7xx preemption support - Adreno A663 support ast: - warn about unsupported TX chips ivpu: - add coredump - add pantherlake support rockchip: - 4K@60Hz display enablement - generate pll programming tables panthor: - add timestamp query API - add realtime group priority - add fdinfo support etnaviv: - improve handling of DMA address limits - improve GPU hangcheck exynos: - Decon Exynos7870 support mediatek: - add OF graph support omap: - locking fixes bochs: - convert to gem/shmem from simpledrm v3d: - support big/super pages - add gemfs vc4: - BCM2712 support refactoring - add YUV444 format support udmabuf: - folio related fixes nouveau: - add panic support on nv50+ -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEEKbZHaGwW9KfbeusDHTzWXnEhr4FAmc+efwACgkQDHTzWXnE hr6Dyg/9HVVI3lxuWAz9MEt3w+BON5KTJAxg5Zhvc5DwiUbDXghu8sfkUfanDWS5 /MqyPqLt5srXrtKTRDnzEI0Vf8YHeiDEcaydjpshEpCfteHZ7SADpvem8fp6/otV iYt8U6tMcGe9I+M2kwDkOTrKJIiyCKPi5hfBIAkxEAh6806ifPRtLkeMGbaSwBxH x6kZTE9ygGWAY7bAgbmVmm3JwrXG9mYDl9dW3cbi9gZ6PGAXHPZRUPvZoHhvfC2A UVgROH76Spm4rdWYGI3azj+gW3HsdGgUHcysb+lu37i261E+sT7kuV2UYtnOMzr5 igO1RlQ+rcfPYLG4n+oNXDMu5d1OQXELrlQzXptym4Konpd7b/GSeVctWV0wHWuv nG8g7DWAFFnLAdeWqLZpf1Brze33h5+572D3BioWB4LYSEATjwoTwcBKsdRuc4Wk RHxjumCidybTdo/8EB1ElGlH39m/mDQA0scMlVhS/BuiIssfgcBRfltI8S3HzHcW YQYq6xH7F9E3shs3/TYbWR4clm66ZTnZV6ClDfGJolzyF/hbV0rsbeSpDelpooE8 1Js7KuwVa+HvA4jtupY9vqxMTdXWwoGPfuUgKpOAreYibnd1T9Q1zVme/B1bUH05 518IjiMGCxDnBvFWaPT9DcX4zg7pS3yzjw3hGkdz3reUqat0Gy8= =8cUI -----END PGP SIGNATURE----- Merge tag 'drm-next-2024-11-21' of https://gitlab.freedesktop.org/drm/kernel Pull drm updates from Dave Airlie: "There's a lot of rework, the panic helper support is being added to more drivers, v3d gets support for HW superpages, scheduler documentation, drm client and video aperture reworks, some new MAINTAINERS added, amdgpu has the usual lots of IP refactors, Intel has some Pantherlake enablement and xe is getting some SRIOV bits, but just lots of stuff everywhere. core: - split DSC helpers from DP helpers - clang build fixes for drm/mm test - drop simple pipeline support for gem vram - document submission error signaling - move drm_rect to drm core module from kms helper - add default client setup to most drivers - move to video aperture helpers instead of drm ones tests: - new framebuffer tests ttm: - remove swapped and pinned BOs from TTM lru panic: - fix uninit spinlock - add ABGR2101010 support bridge: - add TI TDP158 support - use standard PM OPS dma-fence: - use read_trylock instead of read_lock to help lockdep scheduler: - add errno to sched start to report different errors - add locking to drm_sched_entity_modify_sched - improve documentation xe: - add drm_line_printer - lots of refactoring - Enable Xe2 + PES disaggregation - add new ARL PCI ID - SRIOV development work - fix exec unnecessary implicit fence - define and parse OA sync props - forcewake refactoring i915: - Enable BMG/LNL ultra joiner - Enable 10bpx + CCS scanout on ICL+, fp16/CCS on TGL+ - use DSB for plane/color mgmt - Arrow lake PCI IDs - lots of i915/xe display refactoring - enable PXP GuC autoteardown - Pantherlake (PTL) Xe3 LPD display enablement - Allow fastset HDR infoframe changes - write DP source OUI for non-eDP sinks - share PCI IDs between i915 and xe amdgpu: - SDMA queue reset support - SMU 13.0.6, JPEG 4.0.3 updates - Initial runtime repartitioning support - rework IP structs for multiple IP instances - Fetch EDID from _DDC if available - SMU13 zero rpm user control - lots of fixes/cleanups amdkfd: - Increase event FIFO size - add topology cap flag for per queue reset msm: - DPU: - SA8775P support - (disabled by default) MSM8917, MSM8937, MSM8953 and MSM8996 support - Enable large framebuffer support - Drop MSM8998 and SDM845 - DP: - SA8775P support - GPU: - a7xx preemption support - Adreno A663 support ast: - warn about unsupported TX chips ivpu: - add coredump - add pantherlake support rockchip: - 4K@60Hz display enablement - generate pll programming tables panthor: - add timestamp query API - add realtime group priority - add fdinfo support etnaviv: - improve handling of DMA address limits - improve GPU hangcheck exynos: - Decon Exynos7870 support mediatek: - add OF graph support omap: - locking fixes bochs: - convert to gem/shmem from simpledrm v3d: - support big/super pages - add gemfs vc4: - BCM2712 support refactoring - add YUV444 format support udmabuf: - folio related fixes nouveau: - add panic support on nv50+" * tag 'drm-next-2024-11-21' of https://gitlab.freedesktop.org/drm/kernel: (1583 commits) drm/xe/guc: Fix dereference before NULL check drm/amd: Fix initialization mistake for NBIO 7.7.0 Revert "drm/amd/display: parse umc_info or vram_info based on ASIC" drm/amd/display: Fix failure to read vram info due to static BP_RESULT drm/amdgpu: enable GTT fallback handling for dGPUs only drm/amd/amdgpu: limit single process inside MES drm/fourcc: add AMD_FMT_MOD_TILE_GFX9_4K_D_X drm/amdgpu/mes12: correct kiq unmap latency drm/amdgpu: Support vcn and jpeg error info parsing drm/amd : Update MES API header file for v11 & v12 drm/amd/amdkfd: add/remove kfd queues on start/stop KFD scheduling drm/amdkfd: change kfd process kref count at creation drm/amdgpu: Cleanup shift coding style drm/amd/amdgpu: Increase MES log buffer to dump mes scratch data drm/amdgpu: Implement virt req_ras_err_count drm/amdgpu: VF Query RAS Caps from Host if supported drm/amdgpu: Add msg handlers for SRIOV RAS Telemetry drm/amdgpu: Update SRIOV Exchange Headers for RAS Telemetry Support drm/amd/display: 3.2.309 drm/amd/display: Adjust VSDB parser for replay feature ...
811 lines
22 KiB
C
811 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <linux/bug.h>
|
|
#include <linux/aperture.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_client_setup.h>
|
|
#include <drm/drm_damage_helper.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_fbdev_shmem.h>
|
|
#include <drm/drm_fourcc.h>
|
|
#include <drm/drm_framebuffer.h>
|
|
#include <drm/drm_gem_atomic_helper.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_gem_shmem_helper.h>
|
|
#include <drm/drm_managed.h>
|
|
#include <drm/drm_module.h>
|
|
#include <drm/drm_plane_helper.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
|
|
#include <video/vga.h>
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
#define VBE_DISPI_IOPORT_INDEX 0x01CE
|
|
#define VBE_DISPI_IOPORT_DATA 0x01CF
|
|
|
|
#define VBE_DISPI_INDEX_ID 0x0
|
|
#define VBE_DISPI_INDEX_XRES 0x1
|
|
#define VBE_DISPI_INDEX_YRES 0x2
|
|
#define VBE_DISPI_INDEX_BPP 0x3
|
|
#define VBE_DISPI_INDEX_ENABLE 0x4
|
|
#define VBE_DISPI_INDEX_BANK 0x5
|
|
#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6
|
|
#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7
|
|
#define VBE_DISPI_INDEX_X_OFFSET 0x8
|
|
#define VBE_DISPI_INDEX_Y_OFFSET 0x9
|
|
#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa
|
|
|
|
#define VBE_DISPI_ID0 0xB0C0
|
|
#define VBE_DISPI_ID1 0xB0C1
|
|
#define VBE_DISPI_ID2 0xB0C2
|
|
#define VBE_DISPI_ID3 0xB0C3
|
|
#define VBE_DISPI_ID4 0xB0C4
|
|
#define VBE_DISPI_ID5 0xB0C5
|
|
|
|
#define VBE_DISPI_DISABLED 0x00
|
|
#define VBE_DISPI_ENABLED 0x01
|
|
#define VBE_DISPI_GETCAPS 0x02
|
|
#define VBE_DISPI_8BIT_DAC 0x20
|
|
#define VBE_DISPI_LFB_ENABLED 0x40
|
|
#define VBE_DISPI_NOCLEARMEM 0x80
|
|
|
|
static int bochs_modeset = -1;
|
|
static int defx = 1024;
|
|
static int defy = 768;
|
|
|
|
module_param_named(modeset, bochs_modeset, int, 0444);
|
|
MODULE_PARM_DESC(modeset, "enable/disable kernel modesetting");
|
|
|
|
module_param(defx, int, 0444);
|
|
module_param(defy, int, 0444);
|
|
MODULE_PARM_DESC(defx, "default x resolution");
|
|
MODULE_PARM_DESC(defy, "default y resolution");
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
enum bochs_types {
|
|
BOCHS_QEMU_STDVGA,
|
|
BOCHS_SIMICS,
|
|
BOCHS_UNKNOWN,
|
|
};
|
|
|
|
struct bochs_device {
|
|
struct drm_device dev;
|
|
|
|
/* hw */
|
|
void __iomem *mmio;
|
|
int ioports;
|
|
void __iomem *fb_map;
|
|
unsigned long fb_base;
|
|
unsigned long fb_size;
|
|
unsigned long qext_size;
|
|
|
|
/* mode */
|
|
u16 xres;
|
|
u16 yres;
|
|
u16 yres_virtual;
|
|
u32 stride;
|
|
u32 bpp;
|
|
|
|
/* drm */
|
|
struct drm_plane primary_plane;
|
|
struct drm_crtc crtc;
|
|
struct drm_encoder encoder;
|
|
struct drm_connector connector;
|
|
};
|
|
|
|
static struct bochs_device *to_bochs_device(const struct drm_device *dev)
|
|
{
|
|
return container_of(dev, struct bochs_device, dev);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static __always_inline bool bochs_uses_mmio(struct bochs_device *bochs)
|
|
{
|
|
return !IS_ENABLED(CONFIG_HAS_IOPORT) || bochs->mmio;
|
|
}
|
|
|
|
static void bochs_vga_writeb(struct bochs_device *bochs, u16 ioport, u8 val)
|
|
{
|
|
if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df))
|
|
return;
|
|
|
|
if (bochs_uses_mmio(bochs)) {
|
|
int offset = ioport - 0x3c0 + 0x400;
|
|
|
|
writeb(val, bochs->mmio + offset);
|
|
} else {
|
|
outb(val, ioport);
|
|
}
|
|
}
|
|
|
|
static u8 bochs_vga_readb(struct bochs_device *bochs, u16 ioport)
|
|
{
|
|
if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df))
|
|
return 0xff;
|
|
|
|
if (bochs_uses_mmio(bochs)) {
|
|
int offset = ioport - 0x3c0 + 0x400;
|
|
|
|
return readb(bochs->mmio + offset);
|
|
} else {
|
|
return inb(ioport);
|
|
}
|
|
}
|
|
|
|
static u16 bochs_dispi_read(struct bochs_device *bochs, u16 reg)
|
|
{
|
|
u16 ret = 0;
|
|
|
|
if (bochs_uses_mmio(bochs)) {
|
|
int offset = 0x500 + (reg << 1);
|
|
|
|
ret = readw(bochs->mmio + offset);
|
|
} else {
|
|
outw(reg, VBE_DISPI_IOPORT_INDEX);
|
|
ret = inw(VBE_DISPI_IOPORT_DATA);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void bochs_dispi_write(struct bochs_device *bochs, u16 reg, u16 val)
|
|
{
|
|
if (bochs_uses_mmio(bochs)) {
|
|
int offset = 0x500 + (reg << 1);
|
|
|
|
writew(val, bochs->mmio + offset);
|
|
} else {
|
|
outw(reg, VBE_DISPI_IOPORT_INDEX);
|
|
outw(val, VBE_DISPI_IOPORT_DATA);
|
|
}
|
|
}
|
|
|
|
static void bochs_hw_set_big_endian(struct bochs_device *bochs)
|
|
{
|
|
if (bochs->qext_size < 8)
|
|
return;
|
|
|
|
writel(0xbebebebe, bochs->mmio + 0x604);
|
|
}
|
|
|
|
static void bochs_hw_set_little_endian(struct bochs_device *bochs)
|
|
{
|
|
if (bochs->qext_size < 8)
|
|
return;
|
|
|
|
writel(0x1e1e1e1e, bochs->mmio + 0x604);
|
|
}
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
#define bochs_hw_set_native_endian(_b) bochs_hw_set_big_endian(_b)
|
|
#else
|
|
#define bochs_hw_set_native_endian(_b) bochs_hw_set_little_endian(_b)
|
|
#endif
|
|
|
|
static int bochs_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
|
|
{
|
|
struct bochs_device *bochs = data;
|
|
size_t i, start = block * EDID_LENGTH;
|
|
|
|
if (!bochs->mmio)
|
|
return -1;
|
|
|
|
if (start + len > 0x400 /* vga register offset */)
|
|
return -1;
|
|
|
|
for (i = 0; i < len; i++)
|
|
buf[i] = readb(bochs->mmio + start + i);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct drm_edid *bochs_hw_read_edid(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct bochs_device *bochs = to_bochs_device(dev);
|
|
u8 header[8];
|
|
|
|
/* check header to detect whenever edid support is enabled in qemu */
|
|
bochs_get_edid_block(bochs, header, 0, ARRAY_SIZE(header));
|
|
if (drm_edid_header_is_valid(header) != 8)
|
|
return NULL;
|
|
|
|
drm_dbg(dev, "Found EDID data blob.\n");
|
|
|
|
return drm_edid_read_custom(connector, bochs_get_edid_block, bochs);
|
|
}
|
|
|
|
static int bochs_hw_init(struct bochs_device *bochs)
|
|
{
|
|
struct drm_device *dev = &bochs->dev;
|
|
struct pci_dev *pdev = to_pci_dev(dev->dev);
|
|
unsigned long addr, size, mem, ioaddr, iosize;
|
|
u16 id;
|
|
|
|
if (pdev->resource[2].flags & IORESOURCE_MEM) {
|
|
ioaddr = pci_resource_start(pdev, 2);
|
|
iosize = pci_resource_len(pdev, 2);
|
|
/* mmio bar with vga and bochs registers present */
|
|
if (!devm_request_mem_region(&pdev->dev, ioaddr, iosize, "bochs-drm")) {
|
|
DRM_ERROR("Cannot request mmio region\n");
|
|
return -EBUSY;
|
|
}
|
|
bochs->mmio = devm_ioremap(&pdev->dev, ioaddr, iosize);
|
|
if (bochs->mmio == NULL) {
|
|
DRM_ERROR("Cannot map mmio region\n");
|
|
return -ENOMEM;
|
|
}
|
|
} else if (IS_ENABLED(CONFIG_HAS_IOPORT)) {
|
|
ioaddr = VBE_DISPI_IOPORT_INDEX;
|
|
iosize = 2;
|
|
if (!devm_request_region(&pdev->dev, ioaddr, iosize, "bochs-drm")) {
|
|
DRM_ERROR("Cannot request ioports\n");
|
|
return -EBUSY;
|
|
}
|
|
bochs->ioports = 1;
|
|
} else {
|
|
dev_err(dev->dev, "I/O ports are not supported\n");
|
|
return -EIO;
|
|
}
|
|
|
|
id = bochs_dispi_read(bochs, VBE_DISPI_INDEX_ID);
|
|
mem = bochs_dispi_read(bochs, VBE_DISPI_INDEX_VIDEO_MEMORY_64K)
|
|
* 64 * 1024;
|
|
if ((id & 0xfff0) != VBE_DISPI_ID0) {
|
|
DRM_ERROR("ID mismatch\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if ((pdev->resource[0].flags & IORESOURCE_MEM) == 0)
|
|
return -ENODEV;
|
|
addr = pci_resource_start(pdev, 0);
|
|
size = pci_resource_len(pdev, 0);
|
|
if (addr == 0)
|
|
return -ENODEV;
|
|
if (size != mem) {
|
|
DRM_ERROR("Size mismatch: pci=%ld, bochs=%ld\n",
|
|
size, mem);
|
|
size = min(size, mem);
|
|
}
|
|
|
|
if (!devm_request_mem_region(&pdev->dev, addr, size, "bochs-drm"))
|
|
DRM_WARN("Cannot request framebuffer, boot fb still active?\n");
|
|
|
|
bochs->fb_map = devm_ioremap_wc(&pdev->dev, addr, size);
|
|
if (bochs->fb_map == NULL) {
|
|
DRM_ERROR("Cannot map framebuffer\n");
|
|
return -ENOMEM;
|
|
}
|
|
bochs->fb_base = addr;
|
|
bochs->fb_size = size;
|
|
|
|
DRM_INFO("Found bochs VGA, ID 0x%x.\n", id);
|
|
DRM_INFO("Framebuffer size %ld kB @ 0x%lx, %s @ 0x%lx.\n",
|
|
size / 1024, addr,
|
|
bochs->ioports ? "ioports" : "mmio",
|
|
ioaddr);
|
|
|
|
if (bochs->mmio && pdev->revision >= 2) {
|
|
bochs->qext_size = readl(bochs->mmio + 0x600);
|
|
if (bochs->qext_size < 4 || bochs->qext_size > iosize) {
|
|
bochs->qext_size = 0;
|
|
goto noext;
|
|
}
|
|
DRM_DEBUG("Found qemu ext regs, size %ld\n",
|
|
bochs->qext_size);
|
|
bochs_hw_set_native_endian(bochs);
|
|
}
|
|
|
|
noext:
|
|
return 0;
|
|
}
|
|
|
|
static void bochs_hw_blank(struct bochs_device *bochs, bool blank)
|
|
{
|
|
DRM_DEBUG_DRIVER("hw_blank %d\n", blank);
|
|
/* enable color bit (so VGA_IS1_RC access works) */
|
|
bochs_vga_writeb(bochs, VGA_MIS_W, VGA_MIS_COLOR);
|
|
/* discard ar_flip_flop */
|
|
(void)bochs_vga_readb(bochs, VGA_IS1_RC);
|
|
/* blank or unblank; we need only update index and set 0x20 */
|
|
bochs_vga_writeb(bochs, VGA_ATT_W, blank ? 0 : 0x20);
|
|
}
|
|
|
|
static void bochs_hw_setmode(struct bochs_device *bochs, struct drm_display_mode *mode)
|
|
{
|
|
int idx;
|
|
|
|
if (!drm_dev_enter(&bochs->dev, &idx))
|
|
return;
|
|
|
|
bochs->xres = mode->hdisplay;
|
|
bochs->yres = mode->vdisplay;
|
|
bochs->bpp = 32;
|
|
bochs->stride = mode->hdisplay * (bochs->bpp / 8);
|
|
bochs->yres_virtual = bochs->fb_size / bochs->stride;
|
|
|
|
DRM_DEBUG_DRIVER("%dx%d @ %d bpp, vy %d\n",
|
|
bochs->xres, bochs->yres, bochs->bpp,
|
|
bochs->yres_virtual);
|
|
|
|
bochs_hw_blank(bochs, false);
|
|
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE, 0);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_BPP, bochs->bpp);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_XRES, bochs->xres);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_YRES, bochs->yres);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_BANK, 0);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH, bochs->xres);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_HEIGHT,
|
|
bochs->yres_virtual);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET, 0);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET, 0);
|
|
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_ENABLE,
|
|
VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED);
|
|
|
|
drm_dev_exit(idx);
|
|
}
|
|
|
|
static void bochs_hw_setformat(struct bochs_device *bochs, const struct drm_format_info *format)
|
|
{
|
|
int idx;
|
|
|
|
if (!drm_dev_enter(&bochs->dev, &idx))
|
|
return;
|
|
|
|
DRM_DEBUG_DRIVER("format %c%c%c%c\n",
|
|
(format->format >> 0) & 0xff,
|
|
(format->format >> 8) & 0xff,
|
|
(format->format >> 16) & 0xff,
|
|
(format->format >> 24) & 0xff);
|
|
|
|
switch (format->format) {
|
|
case DRM_FORMAT_XRGB8888:
|
|
bochs_hw_set_little_endian(bochs);
|
|
break;
|
|
case DRM_FORMAT_BGRX8888:
|
|
bochs_hw_set_big_endian(bochs);
|
|
break;
|
|
default:
|
|
/* should not happen */
|
|
DRM_ERROR("%s: Huh? Got framebuffer format 0x%x",
|
|
__func__, format->format);
|
|
break;
|
|
}
|
|
|
|
drm_dev_exit(idx);
|
|
}
|
|
|
|
static void bochs_hw_setbase(struct bochs_device *bochs, int x, int y, int stride, u64 addr)
|
|
{
|
|
unsigned long offset;
|
|
unsigned int vx, vy, vwidth, idx;
|
|
|
|
if (!drm_dev_enter(&bochs->dev, &idx))
|
|
return;
|
|
|
|
bochs->stride = stride;
|
|
offset = (unsigned long)addr +
|
|
y * bochs->stride +
|
|
x * (bochs->bpp / 8);
|
|
vy = offset / bochs->stride;
|
|
vx = (offset % bochs->stride) * 8 / bochs->bpp;
|
|
vwidth = stride * 8 / bochs->bpp;
|
|
|
|
DRM_DEBUG_DRIVER("x %d, y %d, addr %llx -> offset %lx, vx %d, vy %d\n",
|
|
x, y, addr, offset, vx, vy);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_VIRT_WIDTH, vwidth);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_X_OFFSET, vx);
|
|
bochs_dispi_write(bochs, VBE_DISPI_INDEX_Y_OFFSET, vy);
|
|
|
|
drm_dev_exit(idx);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static const uint32_t bochs_primary_plane_formats[] = {
|
|
DRM_FORMAT_XRGB8888,
|
|
DRM_FORMAT_BGRX8888,
|
|
};
|
|
|
|
static int bochs_primary_plane_helper_atomic_check(struct drm_plane *plane,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
|
|
struct drm_crtc *new_crtc = new_plane_state->crtc;
|
|
struct drm_crtc_state *new_crtc_state = NULL;
|
|
int ret;
|
|
|
|
if (new_crtc)
|
|
new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc);
|
|
|
|
ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
|
|
DRM_PLANE_NO_SCALING,
|
|
DRM_PLANE_NO_SCALING,
|
|
false, false);
|
|
if (ret)
|
|
return ret;
|
|
else if (!new_plane_state->visible)
|
|
return 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bochs_primary_plane_helper_atomic_update(struct drm_plane *plane,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_device *dev = plane->dev;
|
|
struct bochs_device *bochs = to_bochs_device(dev);
|
|
struct drm_plane_state *plane_state = plane->state;
|
|
struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
|
|
struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
|
|
struct drm_framebuffer *fb = plane_state->fb;
|
|
struct drm_atomic_helper_damage_iter iter;
|
|
struct drm_rect damage;
|
|
|
|
if (!fb || !bochs->stride)
|
|
return;
|
|
|
|
drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
|
|
drm_atomic_for_each_plane_damage(&iter, &damage) {
|
|
struct iosys_map dst = IOSYS_MAP_INIT_VADDR_IOMEM(bochs->fb_map);
|
|
|
|
iosys_map_incr(&dst, drm_fb_clip_offset(fb->pitches[0], fb->format, &damage));
|
|
drm_fb_memcpy(&dst, fb->pitches, shadow_plane_state->data, fb, &damage);
|
|
}
|
|
|
|
/* Always scanout image at VRAM offset 0 */
|
|
bochs_hw_setbase(bochs,
|
|
plane_state->crtc_x,
|
|
plane_state->crtc_y,
|
|
fb->pitches[0],
|
|
0);
|
|
bochs_hw_setformat(bochs, fb->format);
|
|
}
|
|
|
|
static const struct drm_plane_helper_funcs bochs_primary_plane_helper_funcs = {
|
|
DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
|
|
.atomic_check = bochs_primary_plane_helper_atomic_check,
|
|
.atomic_update = bochs_primary_plane_helper_atomic_update,
|
|
};
|
|
|
|
static const struct drm_plane_funcs bochs_primary_plane_funcs = {
|
|
.update_plane = drm_atomic_helper_update_plane,
|
|
.disable_plane = drm_atomic_helper_disable_plane,
|
|
.destroy = drm_plane_cleanup,
|
|
DRM_GEM_SHADOW_PLANE_FUNCS
|
|
};
|
|
|
|
static void bochs_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
|
|
{
|
|
struct bochs_device *bochs = to_bochs_device(crtc->dev);
|
|
struct drm_crtc_state *crtc_state = crtc->state;
|
|
|
|
bochs_hw_setmode(bochs, &crtc_state->mode);
|
|
}
|
|
|
|
static int bochs_crtc_helper_atomic_check(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
|
|
|
if (!crtc_state->enable)
|
|
return 0;
|
|
|
|
return drm_atomic_helper_check_crtc_primary_plane(crtc_state);
|
|
}
|
|
|
|
static void bochs_crtc_helper_atomic_enable(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
}
|
|
|
|
static void bochs_crtc_helper_atomic_disable(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *crtc_state)
|
|
{
|
|
struct bochs_device *bochs = to_bochs_device(crtc->dev);
|
|
|
|
bochs_hw_blank(bochs, true);
|
|
}
|
|
|
|
static const struct drm_crtc_helper_funcs bochs_crtc_helper_funcs = {
|
|
.mode_set_nofb = bochs_crtc_helper_mode_set_nofb,
|
|
.atomic_check = bochs_crtc_helper_atomic_check,
|
|
.atomic_enable = bochs_crtc_helper_atomic_enable,
|
|
.atomic_disable = bochs_crtc_helper_atomic_disable,
|
|
};
|
|
|
|
static const struct drm_crtc_funcs bochs_crtc_funcs = {
|
|
.reset = drm_atomic_helper_crtc_reset,
|
|
.destroy = drm_crtc_cleanup,
|
|
.set_config = drm_atomic_helper_set_config,
|
|
.page_flip = drm_atomic_helper_page_flip,
|
|
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
|
|
};
|
|
|
|
static const struct drm_encoder_funcs bochs_encoder_funcs = {
|
|
.destroy = drm_encoder_cleanup,
|
|
};
|
|
|
|
static int bochs_connector_helper_get_modes(struct drm_connector *connector)
|
|
{
|
|
const struct drm_edid *edid;
|
|
int count;
|
|
|
|
edid = bochs_hw_read_edid(connector);
|
|
|
|
if (edid) {
|
|
drm_edid_connector_update(connector, edid);
|
|
count = drm_edid_connector_add_modes(connector);
|
|
drm_edid_free(edid);
|
|
} else {
|
|
drm_edid_connector_update(connector, NULL);
|
|
count = drm_add_modes_noedid(connector, 8192, 8192);
|
|
drm_set_preferred_mode(connector, defx, defy);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs bochs_connector_helper_funcs = {
|
|
.get_modes = bochs_connector_helper_get_modes,
|
|
};
|
|
|
|
static const struct drm_connector_funcs bochs_connector_funcs = {
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = drm_connector_cleanup,
|
|
.reset = drm_atomic_helper_connector_reset,
|
|
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
|
};
|
|
|
|
static enum drm_mode_status bochs_mode_config_mode_valid(struct drm_device *dev,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct bochs_device *bochs = to_bochs_device(dev);
|
|
const struct drm_format_info *format = drm_format_info(DRM_FORMAT_XRGB8888);
|
|
u64 pitch;
|
|
|
|
if (drm_WARN_ON(dev, !format))
|
|
return MODE_ERROR;
|
|
|
|
pitch = drm_format_info_min_pitch(format, 0, mode->hdisplay);
|
|
if (!pitch)
|
|
return MODE_BAD_WIDTH;
|
|
if (mode->vdisplay > DIV_ROUND_DOWN_ULL(bochs->fb_size, pitch))
|
|
return MODE_MEM;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static const struct drm_mode_config_funcs bochs_mode_config_funcs = {
|
|
.fb_create = drm_gem_fb_create_with_dirty,
|
|
.mode_valid = bochs_mode_config_mode_valid,
|
|
.atomic_check = drm_atomic_helper_check,
|
|
.atomic_commit = drm_atomic_helper_commit,
|
|
};
|
|
|
|
static int bochs_kms_init(struct bochs_device *bochs)
|
|
{
|
|
struct drm_device *dev = &bochs->dev;
|
|
struct drm_plane *primary_plane;
|
|
struct drm_crtc *crtc;
|
|
struct drm_connector *connector;
|
|
struct drm_encoder *encoder;
|
|
int ret;
|
|
|
|
ret = drmm_mode_config_init(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev->mode_config.max_width = 8192;
|
|
dev->mode_config.max_height = 8192;
|
|
|
|
dev->mode_config.preferred_depth = 24;
|
|
dev->mode_config.quirk_addfb_prefer_host_byte_order = true;
|
|
|
|
dev->mode_config.funcs = &bochs_mode_config_funcs;
|
|
|
|
primary_plane = &bochs->primary_plane;
|
|
ret = drm_universal_plane_init(dev, primary_plane, 0,
|
|
&bochs_primary_plane_funcs,
|
|
bochs_primary_plane_formats,
|
|
ARRAY_SIZE(bochs_primary_plane_formats),
|
|
NULL,
|
|
DRM_PLANE_TYPE_PRIMARY, NULL);
|
|
if (ret)
|
|
return ret;
|
|
drm_plane_helper_add(primary_plane, &bochs_primary_plane_helper_funcs);
|
|
drm_plane_enable_fb_damage_clips(primary_plane);
|
|
|
|
crtc = &bochs->crtc;
|
|
ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL,
|
|
&bochs_crtc_funcs, NULL);
|
|
if (ret)
|
|
return ret;
|
|
drm_crtc_helper_add(crtc, &bochs_crtc_helper_funcs);
|
|
|
|
encoder = &bochs->encoder;
|
|
ret = drm_encoder_init(dev, encoder, &bochs_encoder_funcs,
|
|
DRM_MODE_ENCODER_VIRTUAL, NULL);
|
|
if (ret)
|
|
return ret;
|
|
encoder->possible_crtcs = drm_crtc_mask(crtc);
|
|
|
|
connector = &bochs->connector;
|
|
ret = drm_connector_init(dev, connector, &bochs_connector_funcs,
|
|
DRM_MODE_CONNECTOR_VIRTUAL);
|
|
if (ret)
|
|
return ret;
|
|
drm_connector_helper_add(connector, &bochs_connector_helper_funcs);
|
|
drm_connector_attach_edid_property(connector);
|
|
drm_connector_attach_encoder(connector, encoder);
|
|
|
|
drm_mode_config_reset(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* drm interface */
|
|
|
|
static int bochs_load(struct bochs_device *bochs)
|
|
{
|
|
int ret;
|
|
|
|
ret = bochs_hw_init(bochs);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = bochs_kms_init(bochs);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DRM_GEM_FOPS(bochs_fops);
|
|
|
|
static const struct drm_driver bochs_driver = {
|
|
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
|
|
.fops = &bochs_fops,
|
|
.name = "bochs-drm",
|
|
.desc = "bochs dispi vga interface (qemu stdvga)",
|
|
.date = "20130925",
|
|
.major = 1,
|
|
.minor = 0,
|
|
DRM_GEM_SHMEM_DRIVER_OPS,
|
|
DRM_FBDEV_SHMEM_DRIVER_OPS,
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* pm interface */
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int bochs_pm_suspend(struct device *dev)
|
|
{
|
|
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
|
|
|
return drm_mode_config_helper_suspend(drm_dev);
|
|
}
|
|
|
|
static int bochs_pm_resume(struct device *dev)
|
|
{
|
|
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
|
|
|
return drm_mode_config_helper_resume(drm_dev);
|
|
}
|
|
#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_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
struct bochs_device *bochs;
|
|
struct drm_device *dev;
|
|
int ret;
|
|
|
|
ret = aperture_remove_conflicting_pci_devices(pdev, bochs_driver.name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bochs = devm_drm_dev_alloc(&pdev->dev, &bochs_driver, struct bochs_device, dev);
|
|
if (IS_ERR(bochs))
|
|
return PTR_ERR(bochs);
|
|
dev = &bochs->dev;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret)
|
|
goto err_free_dev;
|
|
|
|
pci_set_drvdata(pdev, dev);
|
|
|
|
ret = bochs_load(bochs);
|
|
if (ret)
|
|
goto err_free_dev;
|
|
|
|
ret = drm_dev_register(dev, 0);
|
|
if (ret)
|
|
goto err_free_dev;
|
|
|
|
drm_client_setup(dev, NULL);
|
|
|
|
return ret;
|
|
|
|
err_free_dev:
|
|
drm_dev_put(dev);
|
|
return ret;
|
|
}
|
|
|
|
static void bochs_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct drm_device *dev = pci_get_drvdata(pdev);
|
|
|
|
drm_dev_unplug(dev);
|
|
drm_atomic_helper_shutdown(dev);
|
|
drm_dev_put(dev);
|
|
}
|
|
|
|
static void bochs_pci_shutdown(struct pci_dev *pdev)
|
|
{
|
|
drm_atomic_helper_shutdown(pci_get_drvdata(pdev));
|
|
}
|
|
|
|
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,
|
|
},
|
|
{
|
|
.vendor = 0x4321,
|
|
.device = 0x1111,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
.driver_data = BOCHS_SIMICS,
|
|
},
|
|
{ /* 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,
|
|
.shutdown = bochs_pci_shutdown,
|
|
.driver.pm = &bochs_pm_ops,
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* module init/exit */
|
|
|
|
drm_module_pci_driver_if_modeset(bochs_pci_driver, bochs_modeset);
|
|
|
|
MODULE_DEVICE_TABLE(pci, bochs_pci_tbl);
|
|
MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
|
|
MODULE_DESCRIPTION("DRM Support for bochs dispi vga interface (qemu stdvga)");
|
|
MODULE_LICENSE("GPL");
|