mirror of
https://github.com/torvalds/linux.git
synced 2026-09-12 12:34:02 +02:00
A whole bunch of fixes for various drivers
- Fix drm_crtc_commit leak when PAGE_FLIP_EVENT is used, - amd: plane blend mode fixes - amdxdna: out-of-bounds access fix, reject commands chains with no commands, handle chained mapping BO failures, refuse to flush an imported BO - atomic-state-helpers: set pixel_blend_mode to prop default on reset - dma-buf: Publish the dma-buf only after copy_to_user succeeds, fix some kernel-doc warnings - ethosu: handle mmio mapping failures, handle storage modes only on hardware that supports it, fix job completion fence cleanup - fastrpc: Publish the dma-buf only after copy_to_user succeeds - gud: Improve TV modes and rotation handling - nouveau: use-after-free fixes, add scanline position support, HDMI and DP fixes, null pointer dereference fix, dmem accounting fixes for large folios, use write-combined maps for coherent - pagemap: Prevent double migration of device pages, Reset migration page count on eviction retry, dma-unmap pages before handling migration errors, use after free fixes - prime: fix prime exports tracing - qaic: out-of-bounds access fix - sysfb: Fix integer overflow, fix constant comparison bug - tegra: Add blend mode properties - virtio: exit path and error handling fixes -----BEGIN PGP SIGNATURE----- iJUEABMJAB0WIQTkHFbLp4ejekA/qfgnX84Zoj2+dgUCapk9RQAKCRAnX84Zoj2+ du3SAX9yHaEcnGDqW4cDdwSG04Q/8om+V24gOepm5HEC1Tfwwn/SthpMUArHyT4e PyIlngsBf0PbIyv63k4tURGMDck7RDUDZCf5YtiUR1HdhXUffTCYBClz+TdZCLpq qLyXJFtOqg== =jD2Q -----END PGP SIGNATURE----- Merge tag 'drm-misc-fixes-2026-09-03' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-fixes A whole bunch of fixes for various drivers - Fix drm_crtc_commit leak when PAGE_FLIP_EVENT is used, - amd: plane blend mode fixes - amdxdna: out-of-bounds access fix, reject commands chains with no commands, handle chained mapping BO failures, refuse to flush an imported BO - atomic-state-helpers: set pixel_blend_mode to prop default on reset - dma-buf: Publish the dma-buf only after copy_to_user succeeds, fix some kernel-doc warnings - ethosu: handle mmio mapping failures, handle storage modes only on hardware that supports it, fix job completion fence cleanup - fastrpc: Publish the dma-buf only after copy_to_user succeeds - gud: Improve TV modes and rotation handling - nouveau: use-after-free fixes, add scanline position support, HDMI and DP fixes, null pointer dereference fix, dmem accounting fixes for large folios, use write-combined maps for coherent - pagemap: Prevent double migration of device pages, Reset migration page count on eviction retry, dma-unmap pages before handling migration errors, use after free fixes - prime: fix prime exports tracing - qaic: out-of-bounds access fix - sysfb: Fix integer overflow, fix constant comparison bug - tegra: Add blend mode properties - virtio: exit path and error handling fixes Signed-off-by: Dave Airlie <airlied@redhat.com> From: Maxime Ripard <mripard@redhat.com> Link: https://patch.msgid.link/apk9X5SkRLS9g4RF@houat
This commit is contained in:
commit
42bc1b92c9
3
.mailmap
3
.mailmap
|
|
@ -19,7 +19,8 @@ Abhinav Kumar <quic_abhinavk@quicinc.com> <abhinavk@codeaurora.org>
|
|||
Ahmad Masri <quic_amasri@quicinc.com> <amasri@codeaurora.org>
|
||||
Adam Oldham <oldhamca@gmail.com>
|
||||
Adam Radford <aradford@gmail.com>
|
||||
Aditya Garg <gargaditya08@proton.me> <gargaditya08@live.com>
|
||||
Aditya Garg <aditya.garg@linux.dev> <gargaditya08@live.com>
|
||||
Aditya Garg <aditya.garg@linux.dev> <gargaditya08@proton.me>
|
||||
Adriana Reus <adi.reus@gmail.com> <adriana.reus@intel.com>
|
||||
Adrian Bunk <bunk@stusta.de>
|
||||
Ajay Kaher <ajay.kaher@broadcom.com> <akaher@vmware.com>
|
||||
|
|
|
|||
|
|
@ -8039,7 +8039,7 @@ F: drivers/gpu/drm/sun4i/sun8i*
|
|||
|
||||
DRM DRIVER FOR APPLE TOUCH BARS
|
||||
M: Aun-Ali Zaidi <admin@kodeit.net>
|
||||
M: Aditya Garg <gargaditya08@proton.me>
|
||||
M: Aditya Garg <aditya.garg@linux.dev>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
S: Maintained
|
||||
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
|
||||
|
|
|
|||
|
|
@ -994,7 +994,7 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx,
|
|||
}
|
||||
|
||||
ccnt = payload->command_count;
|
||||
if (payload_len < struct_size(payload, data, ccnt)) {
|
||||
if (!ccnt || payload_len < struct_size(payload, data, ccnt)) {
|
||||
XDNA_DBG(xdna, "Invalid command count %d", ccnt);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,8 +183,10 @@ int amdxdna_cmd_set_error(struct amdxdna_gem_obj *abo,
|
|||
if (!abo)
|
||||
return -EINVAL;
|
||||
cmd = amdxdna_gem_vmap(abo);
|
||||
if (!cmd)
|
||||
if (!cmd) {
|
||||
amdxdna_gem_put_obj(abo);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
memset(cmd->data, 0xff, abo->mem.size - sizeof(*cmd));
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ struct amdxdna_cmd_chain {
|
|||
u32 submit_index;
|
||||
u32 error_index;
|
||||
u32 reserved[3];
|
||||
u64 data[] __counted_by(command_count);
|
||||
u64 data[];
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1246,6 +1246,9 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size)
|
|||
{
|
||||
u64 end;
|
||||
|
||||
if (is_import_bo(abo))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (offset >= abo->mem.size)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -1253,9 +1256,10 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size)
|
|||
return -EINVAL;
|
||||
|
||||
size = min(abo->mem.size, end) - offset;
|
||||
if (is_import_bo(abo))
|
||||
drm_clflush_sg(abo->base.sgt);
|
||||
else if (amdxdna_gem_vmap(abo))
|
||||
if (!size)
|
||||
return 0;
|
||||
|
||||
if (amdxdna_gem_vmap(abo))
|
||||
drm_clflush_virt_range(amdxdna_gem_vmap(abo) + offset, size);
|
||||
else if (abo->base.pages)
|
||||
drm_clflush_pages(abo->base.pages, abo->mem.size >> PAGE_SHIFT);
|
||||
|
|
|
|||
|
|
@ -356,6 +356,8 @@ static int ethosu_probe(struct platform_device *pdev)
|
|||
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
|
||||
|
||||
ethosudev->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(ethosudev->regs))
|
||||
return PTR_ERR(ethosudev->regs);
|
||||
ethosudev->pmu_regs = ethosudev->regs;
|
||||
|
||||
ethosudev->num_clks = devm_clk_bulk_get_all(&pdev->dev, ðosudev->clks);
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
|
|||
struct feat_matrix *fm,
|
||||
u32 x, u32 y, u32 c, bool ofm)
|
||||
{
|
||||
u32 element_size, storage = fm->precision >> 14;
|
||||
u32 element_size, storage = ethosu_is_u65(edev) ? 0 : fm->precision >> 14;
|
||||
int tile = 0;
|
||||
u64 addr;
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,13 @@ static void ethosu_job_err_cleanup(struct ethosu_job *job)
|
|||
|
||||
drm_gem_object_put(job->cmd_bo);
|
||||
|
||||
if (job->done_fence) {
|
||||
if (dma_fence_was_initialized(job->done_fence))
|
||||
dma_fence_put(job->done_fence);
|
||||
else
|
||||
dma_fence_free(job->done_fence);
|
||||
}
|
||||
|
||||
kfree(job);
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +171,6 @@ static void ethosu_job_cleanup(struct kref *ref)
|
|||
|
||||
pm_runtime_put_autosuspend(job->dev->base.dev);
|
||||
|
||||
dma_fence_put(job->done_fence);
|
||||
dma_fence_put(job->inference_done_fence);
|
||||
|
||||
ethosu_job_err_cleanup(job);
|
||||
|
|
@ -415,7 +421,7 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file
|
|||
ejob->done_fence = kzalloc_obj(*ejob->done_fence);
|
||||
if (!ejob->done_fence) {
|
||||
ret = -ENOMEM;
|
||||
goto out_cleanup_job;
|
||||
goto out_put_job;
|
||||
}
|
||||
|
||||
ret = drm_sched_job_init(&ejob->base,
|
||||
|
|
|
|||
|
|
@ -963,11 +963,13 @@ static int decode_status(struct qaic_device *qdev, void *trans, struct manage_ms
|
|||
|
||||
static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg,
|
||||
struct wire_msg *msg, struct ioctl_resources *resources,
|
||||
struct qaic_user *usr)
|
||||
struct qaic_user *usr, bool orphaned_deactivate)
|
||||
{
|
||||
u32 msg_hdr_count = le32_to_cpu(msg->hdr.count);
|
||||
u32 msg_hdr_len = le32_to_cpu(msg->hdr.len);
|
||||
struct wire_trans_hdr *trans_hdr;
|
||||
u32 msg_len = 0;
|
||||
int trans_type;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
|
|
@ -975,10 +977,12 @@ static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg,
|
|||
msg_hdr_len > QAIC_MANAGE_MAX_MSG_LENGTH)
|
||||
return -EINVAL;
|
||||
|
||||
user_msg->len = 0;
|
||||
user_msg->count = le32_to_cpu(msg->hdr.count);
|
||||
if (user_msg) {
|
||||
user_msg->len = 0;
|
||||
user_msg->count = msg_hdr_count;
|
||||
}
|
||||
|
||||
for (i = 0; i < user_msg->count; ++i) {
|
||||
for (i = 0; i < msg_hdr_count; ++i) {
|
||||
u32 hdr_len;
|
||||
|
||||
if (msg_len > msg_hdr_len - sizeof(*trans_hdr))
|
||||
|
|
@ -990,7 +994,20 @@ static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg,
|
|||
size_add(msg_len, hdr_len) > msg_hdr_len)
|
||||
return -EINVAL;
|
||||
|
||||
switch (le32_to_cpu(trans_hdr->type)) {
|
||||
trans_type = le32_to_cpu(trans_hdr->type);
|
||||
/*
|
||||
* orphaned_deactivate is the case where a deactivate response
|
||||
* is received from the device after the user owning the DBC,
|
||||
* and the message requesting deactivation, has gone away.
|
||||
* In this case, only process QAIC_TRANS_DEACTIVATE_FROM_DEV
|
||||
* transaction and skip the others.
|
||||
*/
|
||||
if (orphaned_deactivate && trans_type != QAIC_TRANS_DEACTIVATE_FROM_DEV) {
|
||||
msg_len += hdr_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (trans_type) {
|
||||
case QAIC_TRANS_PASSTHROUGH_FROM_DEV:
|
||||
ret = decode_passthrough(qdev, trans_hdr, user_msg, &msg_len);
|
||||
break;
|
||||
|
|
@ -1281,7 +1298,7 @@ static int qaic_manage(struct qaic_device *qdev, struct qaic_user *usr, struct m
|
|||
goto dma_cont_failed;
|
||||
}
|
||||
|
||||
ret = decode_message(qdev, user_msg, rsp, &resources, usr);
|
||||
ret = decode_message(qdev, user_msg, rsp, &resources, usr, false);
|
||||
|
||||
dma_cont_failed:
|
||||
free_dbc_buf(qdev, &resources);
|
||||
|
|
@ -1446,22 +1463,7 @@ static void resp_worker(struct work_struct *work)
|
|||
* response to the QAIC_TRANS_TERMINATE_TO_DEV transaction,
|
||||
* otherwise, the user can issue an soc_reset to the device.
|
||||
*/
|
||||
u32 msg_count = le32_to_cpu(msg->hdr.count);
|
||||
u32 msg_len = le32_to_cpu(msg->hdr.len);
|
||||
u32 len = 0;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < msg_count && len < msg_len; ++j) {
|
||||
struct wire_trans_hdr *trans_hdr;
|
||||
|
||||
trans_hdr = (struct wire_trans_hdr *)(msg->data + len);
|
||||
if (le32_to_cpu(trans_hdr->type) == QAIC_TRANS_DEACTIVATE_FROM_DEV) {
|
||||
if (decode_deactivate(qdev, trans_hdr, &len, NULL))
|
||||
len += le32_to_cpu(trans_hdr->len);
|
||||
} else {
|
||||
len += le32_to_cpu(trans_hdr->len);
|
||||
}
|
||||
}
|
||||
decode_message(qdev, NULL, msg, NULL, NULL, true);
|
||||
/* request must have timed out, drop packet */
|
||||
kfree(msg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -803,6 +803,26 @@ int dma_buf_fd(struct dma_buf *dmabuf, int flags)
|
|||
}
|
||||
EXPORT_SYMBOL_NS_GPL(dma_buf_fd, "DMA_BUF");
|
||||
|
||||
/**
|
||||
* dma_buf_fd_install - install a reserved fd for a dma-buf
|
||||
* @dmabuf: [in] pointer to dma_buf
|
||||
* @fd: [in] fd reserved with get_unused_fd_flags()
|
||||
*
|
||||
* Publishes a previously reserved fd into the caller's fd table.
|
||||
* Must only be called after all fallible work (e.g. copy_to_user)
|
||||
* has succeeded, as it cannot be undone safely once called.
|
||||
*
|
||||
* The caller is responsible for having emitted the trace event
|
||||
* (via dma_buf_fd() or get_unused_fd_flags() + this function)
|
||||
* before calling this.
|
||||
*/
|
||||
void dma_buf_fd_install(struct dma_buf *dmabuf, int fd)
|
||||
{
|
||||
DMA_BUF_TRACE(trace_dma_buf_fd, dmabuf, fd);
|
||||
fd_install(fd, dmabuf->file);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(dma_buf_fd_install, "DMA_BUF");
|
||||
|
||||
/**
|
||||
* dma_buf_get - returns the struct dma_buf related to an fd
|
||||
* @fd: [in] fd associated with the struct dma_buf to be returned
|
||||
|
|
|
|||
|
|
@ -56,33 +56,6 @@ MODULE_PARM_DESC(mem_accounting,
|
|||
"Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
|
||||
EXPORT_SYMBOL_NS_GPL(mem_accounting, "DMA_BUF_HEAP");
|
||||
|
||||
static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
|
||||
u32 fd_flags,
|
||||
u64 heap_flags)
|
||||
{
|
||||
struct dma_buf *dmabuf;
|
||||
int fd;
|
||||
|
||||
/*
|
||||
* Allocations from all heaps have to begin
|
||||
* and end on page boundaries.
|
||||
*/
|
||||
len = PAGE_ALIGN(len);
|
||||
if (!len)
|
||||
return -EINVAL;
|
||||
|
||||
dmabuf = heap->ops->allocate(heap, len, fd_flags, heap_flags);
|
||||
if (IS_ERR(dmabuf))
|
||||
return PTR_ERR(dmabuf);
|
||||
|
||||
fd = dma_buf_fd(dmabuf, fd_flags);
|
||||
if (fd < 0) {
|
||||
dma_buf_put(dmabuf);
|
||||
/* just return, as put will call release and that will free */
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int dma_heap_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct dma_heap *heap;
|
||||
|
|
@ -100,30 +73,42 @@ static int dma_heap_open(struct inode *inode, struct file *file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static long dma_heap_ioctl_allocate(struct file *file, void *data)
|
||||
static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data)
|
||||
{
|
||||
struct dma_heap_allocation_data *heap_allocation = data;
|
||||
struct dma_heap *heap = file->private_data;
|
||||
struct dma_buf *dmabuf;
|
||||
int fd;
|
||||
size_t len;
|
||||
|
||||
if (heap_allocation->fd)
|
||||
return -EINVAL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS)
|
||||
return -EINVAL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
|
||||
return -EINVAL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
|
||||
heap_allocation->fd_flags,
|
||||
heap_allocation->heap_flags);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
len = PAGE_ALIGN(heap_allocation->len);
|
||||
if (!len)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
dmabuf = heap->ops->allocate(heap, len, heap_allocation->fd_flags,
|
||||
heap_allocation->heap_flags);
|
||||
|
||||
if (IS_ERR(dmabuf))
|
||||
return dmabuf;
|
||||
|
||||
fd = get_unused_fd_flags(heap_allocation->fd_flags);
|
||||
if (fd < 0) {
|
||||
dma_buf_put(dmabuf);
|
||||
return ERR_PTR(fd);
|
||||
}
|
||||
|
||||
heap_allocation->fd = fd;
|
||||
|
||||
return 0;
|
||||
return dmabuf;
|
||||
}
|
||||
|
||||
static unsigned int dma_heap_ioctl_cmds[] = {
|
||||
|
|
@ -139,6 +124,8 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd,
|
|||
unsigned int in_size, out_size, drv_size, ksize;
|
||||
int nr = _IOC_NR(ucmd);
|
||||
int ret = 0;
|
||||
int fd;
|
||||
struct dma_buf *dmabuf;
|
||||
|
||||
if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds))
|
||||
return -EINVAL;
|
||||
|
|
@ -175,15 +162,28 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd,
|
|||
|
||||
switch (kcmd) {
|
||||
case DMA_HEAP_IOCTL_ALLOC:
|
||||
ret = dma_heap_ioctl_allocate(file, kdata);
|
||||
dmabuf = dma_heap_ioctl_allocate(file, kdata);
|
||||
|
||||
if (IS_ERR(dmabuf)) {
|
||||
ret = PTR_ERR(dmabuf);
|
||||
break;
|
||||
}
|
||||
|
||||
fd = ((struct dma_heap_allocation_data *)kdata)->fd;
|
||||
if (copy_to_user((void __user *)arg, kdata, out_size) != 0) {
|
||||
put_unused_fd(fd);
|
||||
dma_buf_put(dmabuf);
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
dma_buf_fd_install(dmabuf, fd);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
ret = -ENOTTY;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (copy_to_user((void __user *)arg, kdata, out_size) != 0)
|
||||
ret = -EFAULT;
|
||||
err:
|
||||
if (kdata != stack_kdata)
|
||||
kfree(kdata);
|
||||
|
|
|
|||
|
|
@ -3879,7 +3879,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state,
|
|||
continue;
|
||||
|
||||
bundle->surface_updates[planes_count].surface = dc_plane;
|
||||
if (new_pcrtc_state->color_mgmt_changed) {
|
||||
if (new_pcrtc_state->color_mgmt_changed || new_plane_state->color_mgmt_changed) {
|
||||
bundle->surface_updates[planes_count].gamma = &dc_plane->gamma_correction;
|
||||
bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func;
|
||||
bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix;
|
||||
|
|
@ -5698,6 +5698,10 @@ static bool should_reset_plane(struct drm_atomic_commit *state,
|
|||
if (new_crtc_state->color_mgmt_changed)
|
||||
return true;
|
||||
|
||||
/* Plane color pipeline or its colorop changes. */
|
||||
if (new_plane_state->color_mgmt_changed)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* On zpos change, planes need to be reordered by removing and re-adding
|
||||
* them one by one to the dc state, in order of descending zpos.
|
||||
|
|
|
|||
|
|
@ -2208,16 +2208,41 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm,
|
|||
if (res)
|
||||
return res;
|
||||
|
||||
if (plane->type == DRM_PLANE_TYPE_OVERLAY &&
|
||||
plane_cap && plane_cap->per_pixel_alpha) {
|
||||
/* Blend mode support varies on DCE generations according to HW caps
|
||||
* and number of planes per CRTC. However, as current driver
|
||||
* implementation only creates one primary and one cursor plane per
|
||||
* CRTC for DCE (overlay is only created if
|
||||
* DC_PLANE_TYPE_DCN_UNIVERSAL), the primary plane blend mode is
|
||||
* ignored across DCE versions. Keep PREMULTI to avoid uAPI
|
||||
* regressions: it was the default/mandatory mode for many years and,
|
||||
* with no overlay plane, primary composes on top of a black
|
||||
* background, where PREMULTI and PIXEL_NONE are equivalent.
|
||||
*/
|
||||
if (plane_cap && plane_cap->type != DC_PLANE_TYPE_DCN_UNIVERSAL) {
|
||||
unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
|
||||
BIT(DRM_MODE_BLEND_PREMULTI);
|
||||
|
||||
drm_plane_create_blend_mode_property(plane, blend_caps);
|
||||
} else if ((plane->type == DRM_PLANE_TYPE_OVERLAY ||
|
||||
plane->type == DRM_PLANE_TYPE_PRIMARY) &&
|
||||
plane_cap && plane_cap->per_pixel_alpha) {
|
||||
unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
|
||||
BIT(DRM_MODE_BLEND_PREMULTI) |
|
||||
BIT(DRM_MODE_BLEND_COVERAGE);
|
||||
|
||||
drm_plane_create_alpha_property(plane);
|
||||
drm_plane_create_blend_mode_property(plane, blend_caps);
|
||||
|
||||
if (plane->type == DRM_PLANE_TYPE_OVERLAY)
|
||||
drm_plane_create_alpha_property(plane);
|
||||
}
|
||||
|
||||
/* Cursor color format is set to CURSOR_MODE_COLOR_PRE_MULTIPLIED_ALPHA
|
||||
* by default, so only advertise DRM_MODE_BLEND_PREMULTI blend mode for
|
||||
* this type of plane.
|
||||
*/
|
||||
if (plane->type == DRM_PLANE_TYPE_CURSOR)
|
||||
drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_PREMULTI));
|
||||
|
||||
if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
|
||||
/*
|
||||
* Allow OVERLAY planes to be used as underlays by assigning an
|
||||
|
|
|
|||
|
|
@ -278,7 +278,14 @@ void __drm_atomic_helper_plane_state_init(struct drm_plane_state *plane_state,
|
|||
plane_state->rotation = DRM_MODE_ROTATE_0;
|
||||
|
||||
plane_state->alpha = DRM_BLEND_ALPHA_OPAQUE;
|
||||
|
||||
plane_state->pixel_blend_mode = DRM_MODE_BLEND_PREMULTI;
|
||||
if (plane->blend_mode_property) {
|
||||
if (!drm_object_property_get_default_value(&plane->base,
|
||||
plane->blend_mode_property,
|
||||
&val))
|
||||
plane_state->pixel_blend_mode = val;
|
||||
}
|
||||
|
||||
if (plane->color_encoding_property) {
|
||||
if (!drm_object_property_get_default_value(&plane->base,
|
||||
|
|
|
|||
|
|
@ -1449,9 +1449,6 @@ static int prepare_signaling(struct drm_device *dev,
|
|||
if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT) {
|
||||
struct drm_pending_vblank_event *e = crtc_state->event;
|
||||
|
||||
if (!file_priv)
|
||||
continue;
|
||||
|
||||
ret = drm_event_reserve_init(dev, file_priv, &e->base,
|
||||
&e->event.base);
|
||||
if (ret) {
|
||||
|
|
@ -1567,6 +1564,8 @@ static void complete_signaling(struct drm_device *dev,
|
|||
* to prevent a double free in drm_atomic_commit_clear.
|
||||
*/
|
||||
if (event && (event->base.fence || event->base.file_priv)) {
|
||||
if (crtc_state->commit && crtc_state->commit->abort_completion)
|
||||
drm_crtc_commit_put(crtc_state->commit);
|
||||
drm_event_cancel_free(dev, &event->base);
|
||||
crtc_state->event = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -383,6 +383,58 @@ drm_pagemap_migrate_map_system_pages(struct device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_pagemap_migrate_populate_src_pages() - Populate the source page array
|
||||
* @pages: Array of source pages to populate
|
||||
* @src_mpfn: Source array of migrate PFNs
|
||||
* @dst_mpfn: Destination array of migrate PFNs
|
||||
* @npages: Number of pages in the arrays
|
||||
*
|
||||
* Populate @pages with the device pages the copy callback is to read from.
|
||||
*
|
||||
* Entries are normally only populated at the head of each source folio, with
|
||||
* the copy callback deriving the rest of the folio from the order recorded in
|
||||
* the corresponding drm_pagemap_addr. That does not work where
|
||||
* drm_pagemap_migrate_populate_ram_pfn() had to demote a higher-order source
|
||||
* folio to order-0 destination folios: the drm_pagemap_addr entries are then
|
||||
* per-page, and the copy callback needs a source page for each of them.
|
||||
* Populate every entry for those ranges.
|
||||
*
|
||||
* Note that the source folio itself is only split later, by
|
||||
* migrate_vma_pages() / migrate_device_pages(), so its order cannot be used to
|
||||
* detect the demotion - the destination has to be inspected instead.
|
||||
*/
|
||||
static void drm_pagemap_migrate_populate_src_pages(struct page **pages,
|
||||
unsigned long *src_mpfn,
|
||||
unsigned long *dst_mpfn,
|
||||
unsigned long npages)
|
||||
{
|
||||
unsigned long i;
|
||||
|
||||
for (i = 0; i < npages;) {
|
||||
struct page *page = migrate_pfn_to_page(src_mpfn[i]);
|
||||
unsigned int order = 0;
|
||||
unsigned long j, nr;
|
||||
|
||||
if (!page) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
order = folio_order(page_folio(page));
|
||||
nr = NR_PAGES(order);
|
||||
|
||||
if (order && !(dst_mpfn[i] & MIGRATE_PFN_COMPOUND)) {
|
||||
for (j = 0; j < nr && i + j < npages; j++)
|
||||
pages[i + j] = folio_page(page_folio(page), j);
|
||||
} else {
|
||||
pages[i] = page;
|
||||
}
|
||||
|
||||
i += nr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_pagemap_migrate_unmap_pages() - Unmap pages previously mapped for GPU SVM migration
|
||||
* @dev: The device for which the pages were mapped
|
||||
|
|
@ -875,6 +927,7 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas,
|
|||
struct page *page = NULL, *src_page;
|
||||
struct folio *folio;
|
||||
unsigned int order = 0;
|
||||
gfp_t gfp = GFP_HIGHUSER;
|
||||
|
||||
if (!(src_mpfn[i] & MIGRATE_PFN_MIGRATE))
|
||||
goto next;
|
||||
|
|
@ -891,11 +944,51 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas,
|
|||
|
||||
order = folio_order(page_folio(src_page));
|
||||
|
||||
/* TODO: Support fallback to single pages if THP allocation fails */
|
||||
/*
|
||||
* A large source folio is always collected whole, at its head
|
||||
* page, PMD aligned and flagged MIGRATE_PFN_COMPOUND: anything
|
||||
* else is split before it reaches us, either by
|
||||
* migrate_vma_collect_pmd() or, for the eviction path, by
|
||||
* migrate_device_pfns(). Both the order-0 fallback below and
|
||||
* drm_pagemap_migrate_populate_src_pages() rely on that, as
|
||||
* they index the folio from @i.
|
||||
*/
|
||||
WARN_ON_ONCE(order &&
|
||||
(src_page != folio_page(page_folio(src_page), 0) ||
|
||||
!(src_mpfn[i] & MIGRATE_PFN_COMPOUND)));
|
||||
|
||||
if (order)
|
||||
gfp |= __GFP_NOWARN;
|
||||
|
||||
if (vas)
|
||||
folio = vma_alloc_folio(GFP_HIGHUSER, order, vas, addr);
|
||||
folio = vma_alloc_folio(gfp, order, vas, addr);
|
||||
else
|
||||
folio = folio_alloc(GFP_HIGHUSER, order);
|
||||
folio = folio_alloc(gfp, order);
|
||||
|
||||
if (!folio && order) {
|
||||
/*
|
||||
* Higher-order allocation failed, fall back to
|
||||
* order-0 allocations for the entire range covered
|
||||
* by the original higher-order allocation, without
|
||||
* setting MIGRATE_PFN_COMPOUND, until we move past
|
||||
* that range.
|
||||
*/
|
||||
unsigned long nr = NR_PAGES(order);
|
||||
unsigned long j;
|
||||
|
||||
gfp &= ~__GFP_NOWARN;
|
||||
for (j = 0; j < nr && i < npages; j++, i++, addr += PAGE_SIZE) {
|
||||
folio = vas ?
|
||||
vma_alloc_folio(gfp, 0, vas, addr) :
|
||||
folio_alloc(gfp, 0);
|
||||
if (!folio)
|
||||
goto free_pages;
|
||||
|
||||
page = folio_page(folio, 0);
|
||||
mpfn[i] = migrate_pfn(page_to_pfn(page));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!folio)
|
||||
goto free_pages;
|
||||
|
|
@ -940,11 +1033,11 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas,
|
|||
if (!page)
|
||||
goto next_put;
|
||||
|
||||
order = folio_order(page_folio(page));
|
||||
|
||||
put_page(page);
|
||||
mpfn[i] = 0;
|
||||
|
||||
order = folio_order(page_folio(page));
|
||||
|
||||
next_put:
|
||||
i += NR_PAGES(order);
|
||||
}
|
||||
|
|
@ -1102,12 +1195,117 @@ void drm_pagemap_put(struct drm_pagemap *dpagemap)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_pagemap_put);
|
||||
|
||||
/**
|
||||
* drm_pagemap_page_get_flags() - Read flags from a device-private folio
|
||||
* @page: Pointer to a page of the device-private folio
|
||||
*
|
||||
* Return: The DRM_PAGEMAP_ZDD_FLAG_* bits encoded in zone_device_data.
|
||||
*/
|
||||
static unsigned long drm_pagemap_page_get_flags(struct page *page)
|
||||
{
|
||||
struct folio *folio = page_folio(page);
|
||||
|
||||
return (unsigned long)folio_zone_device_data(folio) &
|
||||
DRM_PAGEMAP_ZDD_FLAG_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_pagemap_page_set_flags() - Set flags on a device-private folio
|
||||
* @page: Pointer to a page of the device-private folio
|
||||
* @flags: DRM_PAGEMAP_ZDD_FLAG_* bits to set
|
||||
*
|
||||
* Preserve any flags already encoded alongside the ZDD pointer.
|
||||
*/
|
||||
static void drm_pagemap_page_set_flags(struct page *page,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct folio *folio = page_folio(page);
|
||||
unsigned long old;
|
||||
|
||||
if (WARN_ON_ONCE(flags & ~DRM_PAGEMAP_ZDD_FLAG_MASK))
|
||||
return;
|
||||
|
||||
old = (unsigned long)folio_zone_device_data(folio);
|
||||
folio_set_zone_device_data(folio, (void *)(old | flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_pagemap_retire_migrated_pages() - Record migrated device-private folios
|
||||
* @src_pfns: source array after migrate_vma_pages() or migrate_device_pages()
|
||||
* @npages: number of entries in @src_pfns
|
||||
*
|
||||
* Flag device-private folios successfully migrated to RAM before finalize
|
||||
* unlocks the sources. The migrated state is stored in the physical folio, so
|
||||
* it survives later folio splits and subsequent migrations can skip it.
|
||||
*/
|
||||
static void drm_pagemap_retire_migrated_pages(unsigned long *src_pfns,
|
||||
unsigned long npages)
|
||||
{
|
||||
unsigned long i = 0;
|
||||
|
||||
while (i < npages) {
|
||||
struct page *page = migrate_pfn_to_page(src_pfns[i]);
|
||||
unsigned long nr = 1;
|
||||
|
||||
if (!page) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src_pfns[i] & MIGRATE_PFN_COMPOUND)
|
||||
nr = folio_nr_pages(page_folio(page));
|
||||
|
||||
if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) &&
|
||||
is_device_private_page(page))
|
||||
drm_pagemap_page_set_flags(page,
|
||||
DRM_PAGEMAP_ZDD_FLAG_MIGRATED);
|
||||
|
||||
i += nr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_pagemap_skip_retired_pages() - Skip retired device-private folios
|
||||
* @src_pfns: MIGRATE_PFN-encoded source array
|
||||
* @npages: number of entries in @src_pfns
|
||||
*
|
||||
* Skip source folios already migrated to RAM, identified by the migrated flag
|
||||
* stored in the physical folio's zone_device_data.
|
||||
*/
|
||||
static void drm_pagemap_skip_retired_pages(unsigned long *src_pfns,
|
||||
unsigned long npages)
|
||||
{
|
||||
unsigned long i = 0;
|
||||
|
||||
while (i < npages) {
|
||||
struct page *page = migrate_pfn_to_page(src_pfns[i]);
|
||||
unsigned long nr = 1;
|
||||
|
||||
if (!page) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src_pfns[i] & MIGRATE_PFN_COMPOUND)
|
||||
nr = folio_nr_pages(page_folio(page));
|
||||
|
||||
if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) &&
|
||||
is_device_private_page(page) &&
|
||||
(drm_pagemap_page_get_flags(page) &
|
||||
DRM_PAGEMAP_ZDD_FLAG_MIGRATED))
|
||||
src_pfns[i] &= ~MIGRATE_PFN_MIGRATE;
|
||||
|
||||
i += nr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_pagemap_evict_to_ram() - Evict GPU SVM range to RAM
|
||||
* @devmem_allocation: Pointer to the device memory allocation
|
||||
*
|
||||
* Similar to __drm_pagemap_migrate_to_ram but does not require mmap lock and
|
||||
* migration done via migrate_device_* functions.
|
||||
* Similar to __drm_pagemap_migrate_to_ram(), but uses the
|
||||
* migrate_device_* helpers and does not require the mmap lock.
|
||||
* Device-private PFNs already migrated to RAM by either path are skipped.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
|
|
@ -1115,17 +1313,18 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation)
|
|||
{
|
||||
const struct drm_pagemap_devmem_ops *ops = devmem_allocation->ops;
|
||||
struct drm_pagemap_iova_state state = {};
|
||||
unsigned long npages, mpages = 0;
|
||||
unsigned long npages, mpages;
|
||||
struct page **pages;
|
||||
unsigned long *src, *dst;
|
||||
struct drm_pagemap_addr *pagemap_addr;
|
||||
void *buf;
|
||||
int i, err = 0;
|
||||
int err = 0;
|
||||
unsigned int retry_count = 2;
|
||||
|
||||
npages = devmem_allocation->size >> PAGE_SHIFT;
|
||||
|
||||
retry:
|
||||
mpages = 0;
|
||||
if (!mmget_not_zero(devmem_allocation->mm))
|
||||
return -EFAULT;
|
||||
|
||||
|
|
@ -1148,6 +1347,8 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation)
|
|||
if (err)
|
||||
goto err_free;
|
||||
|
||||
drm_pagemap_skip_retired_pages(src, npages);
|
||||
|
||||
err = drm_pagemap_migrate_populate_ram_pfn(NULL, NULL, npages, &mpages,
|
||||
src, dst, 0);
|
||||
if (err || !mpages)
|
||||
|
|
@ -1160,27 +1361,20 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation)
|
|||
if (err)
|
||||
goto err_finalize;
|
||||
|
||||
for (i = 0; i < npages;) {
|
||||
unsigned int order = 0;
|
||||
|
||||
pages[i] = migrate_pfn_to_page(src[i]);
|
||||
if (pages[i])
|
||||
order = folio_order(page_folio(pages[i]));
|
||||
|
||||
i += NR_PAGES(order);
|
||||
}
|
||||
drm_pagemap_migrate_populate_src_pages(pages, src, dst, npages);
|
||||
|
||||
err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL);
|
||||
if (err)
|
||||
goto err_finalize;
|
||||
|
||||
err_finalize:
|
||||
drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages,
|
||||
DMA_FROM_DEVICE, &state);
|
||||
if (err)
|
||||
drm_pagemap_migration_unlock_put_pages(npages, dst);
|
||||
migrate_device_pages(src, dst, npages);
|
||||
drm_pagemap_retire_migrated_pages(src, npages);
|
||||
migrate_device_finalize(src, dst, npages);
|
||||
drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages,
|
||||
DMA_FROM_DEVICE, &state);
|
||||
|
||||
err_free:
|
||||
kvfree(buf);
|
||||
|
|
@ -1235,7 +1429,7 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas,
|
|||
struct drm_pagemap_addr *pagemap_addr;
|
||||
unsigned long start, end;
|
||||
void *buf;
|
||||
int i, err = 0;
|
||||
int err = 0;
|
||||
|
||||
zdd = drm_pagemap_page_zone_device_data(page);
|
||||
if (time_before64(get_jiffies_64(), zdd->devmem_allocation->timeslice_expiration))
|
||||
|
|
@ -1275,13 +1469,15 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas,
|
|||
if (!migrate.cpages)
|
||||
goto err_free;
|
||||
|
||||
drm_pagemap_skip_retired_pages(migrate.src, npages);
|
||||
|
||||
ops = zdd->devmem_allocation->ops;
|
||||
dev = zdd->devmem_allocation->dev;
|
||||
|
||||
err = drm_pagemap_migrate_populate_ram_pfn(vas, page, npages, &mpages,
|
||||
migrate.src, migrate.dst,
|
||||
start);
|
||||
if (err)
|
||||
if (err || !mpages)
|
||||
goto err_finalize;
|
||||
|
||||
err = drm_pagemap_migrate_map_system_pages(dev, pagemap_addr,
|
||||
|
|
@ -1290,29 +1486,23 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas,
|
|||
if (err)
|
||||
goto err_finalize;
|
||||
|
||||
for (i = 0; i < npages;) {
|
||||
unsigned int order = 0;
|
||||
|
||||
pages[i] = migrate_pfn_to_page(migrate.src[i]);
|
||||
if (pages[i])
|
||||
order = folio_order(page_folio(pages[i]));
|
||||
|
||||
i += NR_PAGES(order);
|
||||
}
|
||||
drm_pagemap_migrate_populate_src_pages(pages, migrate.src, migrate.dst,
|
||||
npages);
|
||||
|
||||
err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL);
|
||||
if (err)
|
||||
goto err_finalize;
|
||||
|
||||
err_finalize:
|
||||
if (err)
|
||||
drm_pagemap_migration_unlock_put_pages(npages, migrate.dst);
|
||||
migrate_vma_pages(&migrate);
|
||||
migrate_vma_finalize(&migrate);
|
||||
if (dev)
|
||||
drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst,
|
||||
npages, DMA_FROM_DEVICE,
|
||||
&state);
|
||||
if (err)
|
||||
drm_pagemap_migration_unlock_put_pages(npages, migrate.dst);
|
||||
migrate_vma_pages(&migrate);
|
||||
drm_pagemap_retire_migrated_pages(migrate.src, npages);
|
||||
migrate_vma_finalize(&migrate);
|
||||
err_free:
|
||||
kvfree(buf);
|
||||
err_out:
|
||||
|
|
@ -1360,13 +1550,19 @@ static vm_fault_t drm_pagemap_migrate_to_ram(struct vm_fault *vmf)
|
|||
static void drm_pagemap_folio_split(struct folio *orig_folio, struct folio *new_folio)
|
||||
{
|
||||
struct drm_pagemap_zdd *zdd;
|
||||
unsigned long orig_data, new_data;
|
||||
|
||||
if (!new_folio)
|
||||
return;
|
||||
|
||||
new_folio->pgmap = orig_folio->pgmap;
|
||||
zdd = folio_zone_device_data(orig_folio);
|
||||
folio_set_zone_device_data(new_folio, drm_pagemap_zdd_get(zdd));
|
||||
|
||||
orig_data = (unsigned long)folio_zone_device_data(orig_folio);
|
||||
zdd = (struct drm_pagemap_zdd *)(orig_data & ~DRM_PAGEMAP_ZDD_FLAG_MASK);
|
||||
|
||||
new_data = (unsigned long)drm_pagemap_zdd_get(zdd);
|
||||
new_data |= orig_data & DRM_PAGEMAP_ZDD_FLAG_MASK;
|
||||
folio_set_zone_device_data(new_folio, (void *)new_data);
|
||||
}
|
||||
|
||||
static const struct dev_pagemap_ops drm_pagemap_pagemap_ops = {
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ int drm_gem_prime_handle_to_fd(struct drm_device *dev,
|
|||
return PTR_ERR(dmabuf);
|
||||
}
|
||||
|
||||
fd_install(fd, dmabuf->file);
|
||||
dma_buf_fd_install(dmabuf, fd);
|
||||
*prime_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -396,8 +396,16 @@ static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connect
|
|||
}
|
||||
|
||||
num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN;
|
||||
for (i = 0; i < num_modes; i++)
|
||||
modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN];
|
||||
for (i = 0; i < num_modes; i++) {
|
||||
char *mode = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN];
|
||||
|
||||
if (!memchr(mode, '\0', GUD_CONNECTOR_TV_MODE_NAME_LEN)) {
|
||||
ret = -EIO;
|
||||
goto free;
|
||||
}
|
||||
|
||||
modes[i] = mode;
|
||||
}
|
||||
|
||||
ret = drm_mode_create_tv_properties_legacy(connector->dev, num_modes, modes);
|
||||
free:
|
||||
|
|
|
|||
|
|
@ -289,6 +289,8 @@ static int gud_plane_add_properties(struct gud_device *gdrm)
|
|||
* but mask out any additions on future devices.
|
||||
*/
|
||||
val &= GUD_ROTATION_MASK;
|
||||
if (!(val & GUD_ROTATION_0))
|
||||
continue;
|
||||
ret = drm_plane_create_rotation_property(&gdrm->plane,
|
||||
DRM_MODE_ROTATE_0, val);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -87,4 +87,5 @@ int gp102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct
|
|||
int gv100_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
|
||||
int tu102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
|
||||
int ga102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
|
||||
int gb202_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ nouveau_channel_del(struct nouveau_channel **pchan)
|
|||
{
|
||||
struct nouveau_channel *chan = *pchan;
|
||||
if (chan) {
|
||||
/*
|
||||
* Drop the kill-event subscription first. Its handler
|
||||
* dereferences chan->fence, which the fence context teardown
|
||||
* below frees, so leaving it armed across the teardown leaves
|
||||
* a window for a use-after-free.
|
||||
*/
|
||||
nvif_event_dtor(&chan->kill);
|
||||
|
||||
if (chan->fence)
|
||||
nouveau_fence(chan->cli->drm)->context_del(chan);
|
||||
|
||||
|
|
@ -100,7 +108,6 @@ nouveau_channel_del(struct nouveau_channel **pchan)
|
|||
nvif_object_dtor(&chan->nvsw);
|
||||
nvif_object_dtor(&chan->gart);
|
||||
nvif_object_dtor(&chan->vram);
|
||||
nvif_event_dtor(&chan->kill);
|
||||
nvif_object_dtor(&chan->user);
|
||||
nvif_mem_dtor(&chan->mem_userd);
|
||||
nouveau_vma_del(&chan->sema.vma);
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct vm_fault *vmf)
|
|||
nouveau_fence_new(&fence, dmem->migrate.chan);
|
||||
migrate_vma_pages(&args);
|
||||
nouveau_dmem_fence_done(&fence);
|
||||
dma_unmap_page(drm->dev->dev, dma_info.dma_addr, PAGE_SIZE,
|
||||
dma_unmap_page(drm->dev->dev, dma_info.dma_addr, dma_info.size,
|
||||
DMA_BIDIRECTIONAL);
|
||||
done:
|
||||
migrate_vma_finalize(&args);
|
||||
|
|
@ -279,11 +279,25 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct vm_fault *vmf)
|
|||
|
||||
static void nouveau_dmem_folio_split(struct folio *head, struct folio *tail)
|
||||
{
|
||||
struct nouveau_dmem_chunk *chunk;
|
||||
struct nouveau_dmem *dmem;
|
||||
|
||||
if (tail == NULL)
|
||||
return;
|
||||
tail->pgmap = head->pgmap;
|
||||
tail->mapping = head->mapping;
|
||||
folio_set_zone_device_data(tail, folio_zone_device_data(head));
|
||||
|
||||
/*
|
||||
* The split hands out a new independently-freeable folio that will
|
||||
* later be released via nouveau_dmem_folio_free(); account for it so
|
||||
* chunk->callocated stays balanced.
|
||||
*/
|
||||
chunk = nouveau_page_to_chunk(&head->page);
|
||||
dmem = chunk->drm->dmem;
|
||||
spin_lock(&dmem->lock);
|
||||
chunk->callocated++;
|
||||
spin_unlock(&dmem->lock);
|
||||
}
|
||||
|
||||
static const struct dev_pagemap_ops nouveau_dmem_pagemap_ops = {
|
||||
|
|
@ -772,7 +786,7 @@ static unsigned long nouveau_dmem_migrate_copy_one(struct nouveau_drm *drm,
|
|||
return mpfn;
|
||||
|
||||
out_dma_unmap:
|
||||
dma_unmap_page(dev, dma_info->dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL);
|
||||
dma_unmap_page(dev, dma_info->dma_addr, dma_info->size, DMA_BIDIRECTIONAL);
|
||||
out_free_page:
|
||||
nouveau_dmem_page_free_locked(drm, dpage);
|
||||
out:
|
||||
|
|
|
|||
|
|
@ -72,9 +72,7 @@ nouveau_sgdma_create_ttm(struct ttm_buffer_object *bo, uint32_t page_flags)
|
|||
struct nouveau_sgdma_be *nvbe;
|
||||
enum ttm_caching caching;
|
||||
|
||||
if (nvbo->force_coherent)
|
||||
caching = ttm_uncached;
|
||||
else if (drm->agp.bridge)
|
||||
if (nvbo->force_coherent || drm->agp.bridge)
|
||||
caching = ttm_write_combined;
|
||||
else
|
||||
caching = ttm_cached;
|
||||
|
|
|
|||
|
|
@ -1319,6 +1319,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
|
|||
op->va.range);
|
||||
if (!op->reg || op->reg->dirty) {
|
||||
ret = -ENOENT;
|
||||
op->reg = NULL;
|
||||
goto unwind_continue;
|
||||
}
|
||||
|
||||
|
|
@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
|
|||
op->va.range);
|
||||
if (IS_ERR(op->ops)) {
|
||||
ret = PTR_ERR(op->ops);
|
||||
op->reg = NULL;
|
||||
goto unwind_continue;
|
||||
}
|
||||
|
||||
|
|
@ -1473,6 +1475,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
|
|||
op->va.range);
|
||||
break;
|
||||
case OP_UNMAP_SPARSE:
|
||||
op->reg->dirty = false;
|
||||
__nouveau_uvma_region_insert(uvmm, op->reg);
|
||||
nouveau_uvmm_sm_unmap_prepare_unwind(uvmm, &op->new,
|
||||
op->ops);
|
||||
|
|
@ -1489,7 +1492,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
|
|||
break;
|
||||
}
|
||||
|
||||
drm_gpuva_ops_free(&uvmm->base, op->ops);
|
||||
if (!IS_ERR_OR_NULL(op->ops))
|
||||
drm_gpuva_ops_free(&uvmm->base, op->ops);
|
||||
op->ops = NULL;
|
||||
op->reg = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2846,7 +2846,7 @@ nv1b2_chipset = {
|
|||
.pci = { 0x00000001, gh100_pci_new },
|
||||
.timer = { 0x00000001, gk20a_timer_new },
|
||||
.vfn = { 0x00000001, ga100_vfn_new },
|
||||
.disp = { 0x00000001, ga102_disp_new },
|
||||
.disp = { 0x00000001, gb202_disp_new },
|
||||
.fifo = { 0x00000001, ga102_fifo_new },
|
||||
};
|
||||
|
||||
|
|
@ -2862,7 +2862,7 @@ nv1b3_chipset = {
|
|||
.pci = { 0x00000001, gh100_pci_new },
|
||||
.timer = { 0x00000001, gk20a_timer_new },
|
||||
.vfn = { 0x00000001, ga100_vfn_new },
|
||||
.disp = { 0x00000001, ga102_disp_new },
|
||||
.disp = { 0x00000001, gb202_disp_new },
|
||||
.fifo = { 0x00000001, ga102_fifo_new },
|
||||
};
|
||||
|
||||
|
|
@ -2878,7 +2878,7 @@ nv1b5_chipset = {
|
|||
.pci = { 0x00000001, gh100_pci_new },
|
||||
.timer = { 0x00000001, gk20a_timer_new },
|
||||
.vfn = { 0x00000001, ga100_vfn_new },
|
||||
.disp = { 0x00000001, ga102_disp_new },
|
||||
.disp = { 0x00000001, gb202_disp_new },
|
||||
.fifo = { 0x00000001, ga102_fifo_new },
|
||||
};
|
||||
|
||||
|
|
@ -2894,7 +2894,7 @@ nv1b6_chipset = {
|
|||
.pci = { 0x00000001, gh100_pci_new },
|
||||
.timer = { 0x00000001, gk20a_timer_new },
|
||||
.vfn = { 0x00000001, ga100_vfn_new },
|
||||
.disp = { 0x00000001, ga102_disp_new },
|
||||
.disp = { 0x00000001, gb202_disp_new },
|
||||
.fifo = { 0x00000001, ga102_fifo_new },
|
||||
};
|
||||
|
||||
|
|
@ -2910,7 +2910,7 @@ nv1b7_chipset = {
|
|||
.pci = { 0x00000001, gh100_pci_new },
|
||||
.timer = { 0x00000001, gk20a_timer_new },
|
||||
.vfn = { 0x00000001, ga100_vfn_new },
|
||||
.disp = { 0x00000001, ga102_disp_new },
|
||||
.disp = { 0x00000001, gb202_disp_new },
|
||||
.fifo = { 0x00000001, ga102_fifo_new },
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ nvkm-y += nvkm/engine/disp/gp102.o
|
|||
nvkm-y += nvkm/engine/disp/gv100.o
|
||||
nvkm-y += nvkm/engine/disp/tu102.o
|
||||
nvkm-y += nvkm/engine/disp/ga102.o
|
||||
nvkm-y += nvkm/engine/disp/gb202.o
|
||||
|
||||
nvkm-y += nvkm/engine/disp/udisp.o
|
||||
nvkm-y += nvkm/engine/disp/uconn.o
|
||||
|
|
|
|||
|
|
@ -144,12 +144,23 @@ ga102_disp = {
|
|||
},
|
||||
};
|
||||
|
||||
static const struct nvkm_disp_func
|
||||
ga102_gsp_disp = {
|
||||
.uevent = &gv100_disp_chan_uevent,
|
||||
.ramht_size = 0x2000,
|
||||
.gsp.intr = tu102_disp_intr,
|
||||
.gsp.head = &tu102_gsp_head,
|
||||
.gsp.hdmi_gcp = tu102_sor_hdmi_gcp,
|
||||
.gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
|
||||
.gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
|
||||
};
|
||||
|
||||
int
|
||||
ga102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
|
||||
struct nvkm_disp **pdisp)
|
||||
{
|
||||
if (nvkm_gsp_rm(device->gsp))
|
||||
return r535_disp_new(&ga102_disp, device, type, inst, pdisp);
|
||||
return r535_disp_new(&ga102_gsp_disp, device, type, inst, pdisp);
|
||||
|
||||
return nvkm_disp_new_(&ga102_disp, device, type, inst, pdisp);
|
||||
}
|
||||
|
|
|
|||
191
drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
Normal file
191
drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
* Copyright 2026 Valve Corp.
|
||||
*/
|
||||
#include "priv.h"
|
||||
#include "head.h"
|
||||
#include "ior.h"
|
||||
|
||||
#include <subdev/timer.h>
|
||||
|
||||
/* GB20x (NVD5.0) reorganised the SF HDMI packet units. The AVI unit is
|
||||
* unchanged from GV100, but the legacy VSI unit is gone. Vendor infoframes
|
||||
* are sent through the shared generic infoframe units instead. Register
|
||||
* layout per NVIDIA's clc971.h/clca71.h, programming sequence per
|
||||
* nvhdmipkt_C971.c:programAdvancedInfoframeC971().
|
||||
*/
|
||||
static void
|
||||
gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
|
||||
{
|
||||
struct nvkm_device *device = ior->disp->engine.subdev.device;
|
||||
const u32 hoff = head * 0x400;
|
||||
/* Generic infoframe unit 1, the slot NVIDIA's driver uses for the VSI. */
|
||||
const u32 ctrl = 0x6f0138 + hoff;
|
||||
u8 buf[36] = {};
|
||||
int i;
|
||||
|
||||
/* Disable the unit and wait for it to go idle. */
|
||||
nvkm_mask(device, ctrl, 0x00000001, 0x00000000);
|
||||
if (nvkm_msec(device, 2000,
|
||||
if (!(nvkm_rd32(device, ctrl) & 0x00400000))
|
||||
break;
|
||||
) < 0)
|
||||
return;
|
||||
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
/* Clear SENT status, and point the data port at unit 1's slot. */
|
||||
nvkm_mask(device, ctrl, 0x00800000, 0x00800000);
|
||||
nvkm_wr32(device, 0x6f03f0 + hoff, 0x00000001);
|
||||
|
||||
/* The data port takes the raw packet, except that a zero is inserted
|
||||
* in HB3 after the three header bytes. A slot is 9 dwords (HB0-3 plus
|
||||
* up to 32 payload bytes). An HDMI infoframe carries at most PB0-27,
|
||||
* so the tail stays zero, and we always write the whole slot.
|
||||
*/
|
||||
size = min_t(u32, size, 31);
|
||||
memcpy(buf, data, min_t(u32, size, 3));
|
||||
if (size > 3)
|
||||
memcpy(&buf[4], (u8 *)data + 3, size - 3);
|
||||
|
||||
for (i = 0; i < 36; i += 4) {
|
||||
nvkm_wr32(device, 0x6f03f4 + hoff, buf[i + 0] | buf[i + 1] << 8 |
|
||||
buf[i + 2] << 16 |
|
||||
(u32)buf[i + 3] << 24);
|
||||
}
|
||||
|
||||
/* No flip ID or scanline matching. */
|
||||
nvkm_wr32(device, 0x6f013c + hoff, 0x00000000);
|
||||
|
||||
/* ENABLE | RUN_MODE=ALWAYS | LOC=VBLANK | OFFSET=1 | SIZE=0. */
|
||||
nvkm_wr32(device, ctrl, 0x00000041);
|
||||
|
||||
/* Audio priority low (the init value). */
|
||||
nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002);
|
||||
}
|
||||
|
||||
/* General Control Packet AVMute bracket. The GCP unit moved to slot 1 on
|
||||
* NVD5.0. Only SB0 (the AVMute bit) is ours to write so we must not do a
|
||||
* full write here: SB1 carries the deep-color CD/PP fields, and SB1_CTRL
|
||||
* (bit 24, new with clc871.h) controls where their generation happens (HW
|
||||
* or driver) on these chips, with the default being HW.
|
||||
*/
|
||||
static void
|
||||
gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable)
|
||||
{
|
||||
struct nvkm_device *device = sor->disp->engine.subdev.device;
|
||||
const u32 hdmi = head * 0x400;
|
||||
|
||||
nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000000);
|
||||
nvkm_mask(device, 0x6f004c + hdmi, 0x000000ff, !enable ? 0x00000001 :
|
||||
0x00000010);
|
||||
nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001);
|
||||
}
|
||||
|
||||
/* Same core-channel state mirror as gv100_head_state() (assembly at 0x680000,
|
||||
* armed at +0x8000, per-head method offsets unchanged), but NVD5.0 spaces
|
||||
* heads 0x800 apart (see NVCA7D_HEAD_SET_*(a) in clca7d.h).
|
||||
*/
|
||||
static void
|
||||
gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
const u32 hoff = (state == &head->arm) * 0x8000 + head->id * 0x800;
|
||||
u32 data;
|
||||
|
||||
data = nvkm_rd32(device, 0x682064 + hoff);
|
||||
state->vtotal = (data & 0xffff0000) >> 16;
|
||||
state->htotal = (data & 0x0000ffff);
|
||||
data = nvkm_rd32(device, 0x682068 + hoff);
|
||||
state->vsynce = (data & 0xffff0000) >> 16;
|
||||
state->hsynce = (data & 0x0000ffff);
|
||||
data = nvkm_rd32(device, 0x68206c + hoff);
|
||||
state->vblanke = (data & 0xffff0000) >> 16;
|
||||
state->hblanke = (data & 0x0000ffff);
|
||||
data = nvkm_rd32(device, 0x682070 + hoff);
|
||||
state->vblanks = (data & 0xffff0000) >> 16;
|
||||
state->hblanks = (data & 0x0000ffff);
|
||||
/* Bit 31 is ADJ1000DIV1001, not a HERTZ bit. We don't have enough bits
|
||||
* to add the full clock in hz on Blackwell (35 bits), but state->hz
|
||||
* is unused and obsolete under GSP so this is fine.
|
||||
*/
|
||||
state->hz = nvkm_rd32(device, 0x68200c + hoff) & 0x7fffffff;
|
||||
|
||||
data = nvkm_rd32(device, 0x682004 + hoff);
|
||||
switch ((data & 0x000000f0) >> 4) {
|
||||
case 5: state->or.depth = 30; break;
|
||||
case 4: state->or.depth = 24; break;
|
||||
case 1: state->or.depth = 18; break;
|
||||
default:
|
||||
state->or.depth = 18;
|
||||
WARN_ON(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* NVD5.0 (GB20x and later) moved the RM head-timing interrupt enable to
|
||||
* the low-latency vector's EN1 block. The event latch is unchanged.
|
||||
*/
|
||||
static void
|
||||
gb202_head_vblank_put(struct nvkm_head *head)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
|
||||
nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000000);
|
||||
}
|
||||
|
||||
static void
|
||||
gb202_head_vblank_get(struct nvkm_head *head)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
|
||||
nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002);
|
||||
nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000002);
|
||||
}
|
||||
|
||||
static irqreturn_t
|
||||
gb202_disp_intr(struct nvkm_inth *inth)
|
||||
{
|
||||
struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
|
||||
irqreturn_t ret = tu102_disp_intr(inth);
|
||||
|
||||
/* The FE interrupt vectors are message-based on NVD5.0. Re-arm the
|
||||
* low-latency vector so it fires again for any event that latched
|
||||
* while we were servicing.
|
||||
*/
|
||||
nvkm_wr32(disp->engine.subdev.device, 0x611f34, 0x00000001);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct nvkm_head_func
|
||||
gb202_gsp_head = {
|
||||
.state = gb202_head_state,
|
||||
.rgpos = gv100_head_rgpos,
|
||||
.vblank_get = gb202_head_vblank_get,
|
||||
.vblank_put = gb202_head_vblank_put,
|
||||
};
|
||||
|
||||
/* GB20x is GSP-only. This table supplies the register programming the
|
||||
* GSP-RM display path needs from the chip.
|
||||
*/
|
||||
static const struct nvkm_disp_func
|
||||
gb202_gsp_disp = {
|
||||
.uevent = &gv100_disp_chan_uevent,
|
||||
.ramht_size = 0x2000,
|
||||
/* Head timing arrives on the dedicated low-latency vector. */
|
||||
.gsp.intr = gb202_disp_intr,
|
||||
.gsp.intr_low_latency = true,
|
||||
.gsp.head = &gb202_gsp_head,
|
||||
.gsp.hdmi_gcp = gb202_sor_hdmi_gcp,
|
||||
/* The legacy AVI unit is unchanged on GB20x. */
|
||||
.gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
|
||||
.gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
|
||||
};
|
||||
|
||||
int
|
||||
gb202_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
|
||||
struct nvkm_disp **pdisp)
|
||||
{
|
||||
return r535_disp_new(&gb202_gsp_disp, device, type, inst, pdisp);
|
||||
}
|
||||
|
|
@ -56,6 +56,8 @@ int gv100_head_new(struct nvkm_disp *, int id);
|
|||
void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state);
|
||||
void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline);
|
||||
|
||||
extern const struct nvkm_head_func tu102_gsp_head;
|
||||
|
||||
#define HEAD_MSG(h,l,f,a...) do { \
|
||||
struct nvkm_head *_h = (h); \
|
||||
nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a); \
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ void gv100_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
|
|||
void gv100_sor_dp_watermark(struct nvkm_ior *, int, u8);
|
||||
extern const struct nvkm_ior_func_hda gv100_sor_hda;
|
||||
|
||||
void tu102_sor_hdmi_gcp(struct nvkm_ior *, int, bool);
|
||||
void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
|
||||
|
||||
int nv50_pior_cnt(struct nvkm_disp *, unsigned long *);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include <engine/disp.h>
|
||||
#include <core/enum.h>
|
||||
struct nvkm_head;
|
||||
struct nvkm_head_func;
|
||||
struct nvkm_ior;
|
||||
struct nvkm_outp;
|
||||
struct dcb_output;
|
||||
|
||||
|
|
@ -34,6 +36,20 @@ struct nvkm_disp_func {
|
|||
int (*new)(struct nvkm_disp *, int id);
|
||||
} wndw, head, dac, sor, pior;
|
||||
|
||||
/* Register programming that the GSP-RM display path (rm/r535) needs from
|
||||
* the chip, everything else on that path goes through RM. The hooks are
|
||||
* called unconditionally and the head table is handed to nvkm_head_new_().
|
||||
*/
|
||||
struct {
|
||||
irqreturn_t (*intr)(struct nvkm_inth *);
|
||||
/* Head-timing interrupts arrive on a second DISP vector. */
|
||||
bool intr_low_latency;
|
||||
const struct nvkm_head_func *head;
|
||||
void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable);
|
||||
void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size);
|
||||
void (*hdmi_infoframe_vsi)(struct nvkm_ior *, int head, void *data, u32 size);
|
||||
} gsp;
|
||||
|
||||
u16 ramht_size;
|
||||
|
||||
struct nvkm_sclass root;
|
||||
|
|
@ -72,6 +88,7 @@ int gv100_disp_wndw_cnt(struct nvkm_disp *, unsigned long *);
|
|||
int gv100_disp_caps_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
|
||||
|
||||
int tu102_disp_init(struct nvkm_disp *);
|
||||
irqreturn_t tu102_disp_intr(struct nvkm_inth *);
|
||||
|
||||
void nv50_disp_dptmds_war_2(struct nvkm_disp *, struct dcb_output *);
|
||||
void nv50_disp_dptmds_war_3(struct nvkm_disp *, struct dcb_output *);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,21 @@
|
|||
|
||||
#include <nvif/class.h>
|
||||
|
||||
/* General Control Packet: bracket an audio enable/disable with AVMute
|
||||
* through the legacy GCP SF unit. Used by the GSP-RM path, which sends the
|
||||
* equivalent packet via RM as well but keeps the direct write in sync.
|
||||
*/
|
||||
void
|
||||
tu102_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable)
|
||||
{
|
||||
struct nvkm_device *device = sor->disp->engine.subdev.device;
|
||||
const u32 hdmi = head * 0x400;
|
||||
|
||||
nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000);
|
||||
nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010);
|
||||
nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
|
||||
}
|
||||
|
||||
void
|
||||
tu102_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned)
|
||||
{
|
||||
|
|
@ -104,6 +119,64 @@ tu102_sor_new(struct nvkm_disp *disp, int id)
|
|||
return nvkm_ior_new_(&tu102_sor, disp, SOR, id, hda & BIT(id));
|
||||
}
|
||||
|
||||
/* The GSP-RM display path leaves head-timing (vblank) interrupts and their
|
||||
* enables to us. These program the RM head-timing line (bit 1 of the
|
||||
* per-head enable, not the bit nvkm's own gv100 path uses).
|
||||
*/
|
||||
static void
|
||||
tu102_head_vblank_put(struct nvkm_head *head)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
|
||||
nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
|
||||
}
|
||||
|
||||
static void
|
||||
tu102_head_vblank_get(struct nvkm_head *head)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
|
||||
nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002);
|
||||
nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
|
||||
}
|
||||
|
||||
const struct nvkm_head_func
|
||||
tu102_gsp_head = {
|
||||
.state = gv100_head_state,
|
||||
.rgpos = gv100_head_rgpos,
|
||||
.vblank_get = tu102_head_vblank_get,
|
||||
.vblank_put = tu102_head_vblank_put,
|
||||
};
|
||||
|
||||
static void
|
||||
tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head)
|
||||
{
|
||||
struct nvkm_subdev *subdev = &disp->engine.subdev;
|
||||
struct nvkm_device *device = subdev->device;
|
||||
u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));
|
||||
|
||||
if (stat & 0x00000002) {
|
||||
nvkm_disp_vblank(disp, head);
|
||||
|
||||
nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
|
||||
}
|
||||
}
|
||||
|
||||
irqreturn_t
|
||||
tu102_disp_intr(struct nvkm_inth *inth)
|
||||
{
|
||||
struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
|
||||
struct nvkm_subdev *subdev = &disp->engine.subdev;
|
||||
struct nvkm_device *device = subdev->device;
|
||||
unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
|
||||
int head;
|
||||
|
||||
for_each_set_bit(head, &mask, 8)
|
||||
tu102_disp_intr_head_timing(disp, head);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int
|
||||
tu102_disp_init(struct nvkm_disp *disp)
|
||||
{
|
||||
|
|
@ -230,12 +303,23 @@ tu102_disp = {
|
|||
},
|
||||
};
|
||||
|
||||
static const struct nvkm_disp_func
|
||||
tu102_gsp_disp = {
|
||||
.uevent = &gv100_disp_chan_uevent,
|
||||
.ramht_size = 0x2000,
|
||||
.gsp.intr = tu102_disp_intr,
|
||||
.gsp.head = &tu102_gsp_head,
|
||||
.gsp.hdmi_gcp = tu102_sor_hdmi_gcp,
|
||||
.gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
|
||||
.gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
|
||||
};
|
||||
|
||||
int
|
||||
tu102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
|
||||
struct nvkm_disp **pdisp)
|
||||
{
|
||||
if (nvkm_gsp_rm(device->gsp))
|
||||
return r535_disp_new(&tu102_disp, device, type, inst, pdisp);
|
||||
return r535_disp_new(&tu102_gsp_disp, device, type, inst, pdisp);
|
||||
|
||||
return nvkm_disp_new_(&tu102_disp, device, type, inst, pdisp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -400,16 +400,16 @@ r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
|
|||
r535_sor_dp_audio_mute(sor, false);
|
||||
}
|
||||
|
||||
static void
|
||||
r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
|
||||
static int
|
||||
r535_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
|
||||
{
|
||||
struct nvkm_disp *disp = sor->disp;
|
||||
struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
|
||||
|
||||
ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
|
||||
NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
|
||||
if (WARN_ON(IS_ERR(ctrl)))
|
||||
return;
|
||||
if (IS_ERR(ctrl))
|
||||
return PTR_ERR(ctrl);
|
||||
|
||||
ctrl->subDeviceInstance = 0;
|
||||
ctrl->head = head;
|
||||
|
|
@ -429,12 +429,20 @@ r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u
|
|||
ctrl->MST.sendACT = 0;
|
||||
ctrl->MST.singleHeadMSTPipeline = 0;
|
||||
ctrl->MST.bEnableAudioOverRightPanel = 0;
|
||||
WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
|
||||
return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
|
||||
}
|
||||
|
||||
static void
|
||||
r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
|
||||
{
|
||||
const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api;
|
||||
|
||||
WARN_ON(rmapi->disp->dp.vcpi(sor, head, slot, slot_nr, pbn, aligned_pbn));
|
||||
}
|
||||
|
||||
static int
|
||||
r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
|
||||
u32 watermark, u32 hblanksym, u32 vblanksym)
|
||||
r535_dp_sst(struct nvkm_ior *sor, int head, bool ef,
|
||||
u32 watermark, u32 hblanksym, u32 vblanksym)
|
||||
{
|
||||
struct nvkm_disp *disp = sor->disp;
|
||||
struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
|
||||
|
|
@ -461,6 +469,15 @@ r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
|
|||
return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
|
||||
}
|
||||
|
||||
static int
|
||||
r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
|
||||
u32 watermark, u32 hblanksym, u32 vblanksym)
|
||||
{
|
||||
const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api;
|
||||
|
||||
return rmapi->disp->dp.sst(sor, head, ef, watermark, hblanksym, vblanksym);
|
||||
}
|
||||
|
||||
static const struct nvkm_ior_func_dp
|
||||
r535_sor_dp = {
|
||||
.sst = r535_sor_dp_sst,
|
||||
|
|
@ -545,16 +562,21 @@ r535_sor_hdmi_ctrl_audio(struct nvkm_outp *outp, bool enable)
|
|||
static void
|
||||
r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
|
||||
{
|
||||
struct nvkm_device *device = sor->disp->engine.subdev.device;
|
||||
const u32 hdmi = head * 0x400;
|
||||
|
||||
r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable);
|
||||
r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable);
|
||||
sor->disp->func->gsp.hdmi_gcp(sor, head, enable);
|
||||
}
|
||||
|
||||
/* General Control (GCP). */
|
||||
nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000);
|
||||
nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010);
|
||||
nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
|
||||
static void
|
||||
r535_sor_hdmi_infoframe_avi(struct nvkm_ior *sor, int head, void *data, u32 size)
|
||||
{
|
||||
sor->disp->func->gsp.hdmi_infoframe_avi(sor, head, data, size);
|
||||
}
|
||||
|
||||
static void
|
||||
r535_sor_hdmi_infoframe_vsi(struct nvkm_ior *sor, int head, void *data, u32 size)
|
||||
{
|
||||
sor->disp->func->gsp.hdmi_infoframe_vsi(sor, head, data, size);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -582,8 +604,8 @@ r535_sor_hdmi = {
|
|||
.ctrl = r535_sor_hdmi_ctrl,
|
||||
.scdc = r535_sor_hdmi_scdc,
|
||||
/*TODO: SF_USER -> KMS. */
|
||||
.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
|
||||
.infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
|
||||
.infoframe_avi = r535_sor_hdmi_infoframe_avi,
|
||||
.infoframe_vsi = r535_sor_hdmi_infoframe_vsi,
|
||||
.audio = r535_sor_hdmi_audio,
|
||||
};
|
||||
|
||||
|
|
@ -608,31 +630,6 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
|
|||
return 4;
|
||||
}
|
||||
|
||||
static void
|
||||
r535_head_vblank_put(struct nvkm_head *head)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
|
||||
nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
|
||||
}
|
||||
|
||||
static void
|
||||
r535_head_vblank_get(struct nvkm_head *head)
|
||||
{
|
||||
struct nvkm_device *device = head->disp->engine.subdev.device;
|
||||
|
||||
nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002);
|
||||
nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
|
||||
}
|
||||
|
||||
static const struct nvkm_head_func
|
||||
r535_head = {
|
||||
.state = gv100_head_state,
|
||||
.rgpos = gv100_head_rgpos,
|
||||
.vblank_get = r535_head_vblank_get,
|
||||
.vblank_put = r535_head_vblank_put,
|
||||
};
|
||||
|
||||
static struct nvkm_conn *
|
||||
r535_conn_new(struct nvkm_disp *disp, u32 id)
|
||||
{
|
||||
|
|
@ -1404,35 +1401,6 @@ static const struct nvkm_event_func
|
|||
r535_disp_event = {
|
||||
};
|
||||
|
||||
static void
|
||||
r535_disp_intr_head_timing(struct nvkm_disp *disp, int head)
|
||||
{
|
||||
struct nvkm_subdev *subdev = &disp->engine.subdev;
|
||||
struct nvkm_device *device = subdev->device;
|
||||
u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));
|
||||
|
||||
if (stat & 0x00000002) {
|
||||
nvkm_disp_vblank(disp, head);
|
||||
|
||||
nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t
|
||||
r535_disp_intr(struct nvkm_inth *inth)
|
||||
{
|
||||
struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
|
||||
struct nvkm_subdev *subdev = &disp->engine.subdev;
|
||||
struct nvkm_device *device = subdev->device;
|
||||
unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
|
||||
int head;
|
||||
|
||||
for_each_set_bit(head, &mask, 8)
|
||||
r535_disp_intr_head_timing(disp, head);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
r535_disp_fini(struct nvkm_disp *disp, bool suspend)
|
||||
{
|
||||
|
|
@ -1659,7 +1627,7 @@ r535_disp_oneinit(struct nvkm_disp *disp)
|
|||
nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
|
||||
|
||||
for_each_set_bit(i, &disp->head.mask, disp->head.nr) {
|
||||
ret = nvkm_head_new_(&r535_head, disp, i);
|
||||
ret = nvkm_head_new_(disp->func->gsp.head, disp, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1703,12 +1671,20 @@ r535_disp_oneinit(struct nvkm_disp *disp)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst);
|
||||
/* Chips that raise head-timing interrupts on a separate low-latency
|
||||
* vector report it as a second DISP interrupt table entry, exposed
|
||||
* as instance 1 by the RM engine-index translation (see
|
||||
* r570_gsp_xlat_mc_engine_idx()). Their high-latency vector
|
||||
* (instance 0) is left unhandled as no event nouveau enables is
|
||||
* routed to it, and without a handler it stays masked.
|
||||
*/
|
||||
ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type,
|
||||
disp->func->gsp.intr_low_latency ? 1 : disp->engine.subdev.inst);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
|
||||
r535_disp_intr, &disp->engine.subdev.inth);
|
||||
disp->func->gsp.intr, &disp->engine.subdev.inth);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1741,6 +1717,7 @@ r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device,
|
|||
rm->uevent = hw->uevent;
|
||||
rm->sor.cnt = r535_sor_cnt;
|
||||
rm->sor.new = r535_sor_new;
|
||||
rm->gsp = hw->gsp;
|
||||
rm->ramht_size = hw->ramht_size;
|
||||
|
||||
rm->root.oclass = gpu->disp.class.root;
|
||||
|
|
@ -1782,6 +1759,8 @@ r535_disp = {
|
|||
.dp = {
|
||||
.get_caps = r535_dp_get_caps,
|
||||
.set_indexed_link_rates = r535_dp_set_indexed_link_rates,
|
||||
.sst = r535_dp_sst,
|
||||
.vcpi = r535_dp_vcpi,
|
||||
},
|
||||
.chan = {
|
||||
.set_pushbuf = r535_disp_chan_set_pushbuf,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <rm/rm.h>
|
||||
|
||||
#include <engine/disp.h>
|
||||
#include <engine/disp/ior.h>
|
||||
#include <engine/disp/outp.h>
|
||||
|
||||
#include "nvhw/drf.h"
|
||||
|
|
@ -74,6 +75,67 @@ r570_disp_chan_set_pushbuf(struct nvkm_disp *disp, s32 oclass, int inst, struct
|
|||
return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl);
|
||||
}
|
||||
|
||||
static int
|
||||
r570_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
|
||||
{
|
||||
struct nvkm_disp *disp = sor->disp;
|
||||
NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
|
||||
|
||||
ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
|
||||
NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
|
||||
if (IS_ERR(ctrl))
|
||||
return PTR_ERR(ctrl);
|
||||
|
||||
ctrl->subDeviceInstance = 0;
|
||||
ctrl->head = head;
|
||||
ctrl->sorIndex = sor->id;
|
||||
ctrl->dpLink = sor->asy.link == 2;
|
||||
ctrl->bEnableOverride = 1;
|
||||
ctrl->bMST = 1;
|
||||
ctrl->hBlankSym = 0;
|
||||
ctrl->vBlankSym = 0;
|
||||
ctrl->colorFormat = 0;
|
||||
ctrl->bEnableTwoHeadOneOr = 0;
|
||||
ctrl->singleHeadMultistreamMode = 0;
|
||||
ctrl->MST.slotStart = slot;
|
||||
ctrl->MST.slotEnd = slot + slot_nr - 1;
|
||||
ctrl->MST.PBN = pbn;
|
||||
ctrl->MST.Timeslice = aligned_pbn;
|
||||
ctrl->MST.sendACT = 0;
|
||||
ctrl->MST.singleHeadMSTPipeline = 0;
|
||||
ctrl->MST.bEnableAudioOverRightPanel = 0;
|
||||
return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
|
||||
}
|
||||
|
||||
static int
|
||||
r570_dp_sst(struct nvkm_ior *sor, int head, bool ef,
|
||||
u32 watermark, u32 hblanksym, u32 vblanksym)
|
||||
{
|
||||
struct nvkm_disp *disp = sor->disp;
|
||||
NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
|
||||
|
||||
ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
|
||||
NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
|
||||
if (IS_ERR(ctrl))
|
||||
return PTR_ERR(ctrl);
|
||||
|
||||
ctrl->subDeviceInstance = 0;
|
||||
ctrl->head = head;
|
||||
ctrl->sorIndex = sor->id;
|
||||
ctrl->dpLink = sor->asy.link == 2;
|
||||
ctrl->bEnableOverride = 1;
|
||||
ctrl->bMST = 0;
|
||||
ctrl->hBlankSym = hblanksym;
|
||||
ctrl->vBlankSym = vblanksym;
|
||||
ctrl->colorFormat = 0;
|
||||
ctrl->bEnableTwoHeadOneOr = 0;
|
||||
ctrl->SST.bEnhancedFraming = ef;
|
||||
ctrl->SST.tuSize = 64;
|
||||
ctrl->SST.waterMark = watermark;
|
||||
ctrl->SST.bEnableAudioOverRightPanel = 0;
|
||||
return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
|
||||
}
|
||||
|
||||
static int
|
||||
r570_dp_set_indexed_link_rates(struct nvkm_outp *outp)
|
||||
{
|
||||
|
|
@ -255,6 +317,8 @@ r570_disp = {
|
|||
.dp = {
|
||||
.get_caps = r570_dp_get_caps,
|
||||
.set_indexed_link_rates = r570_dp_set_indexed_link_rates,
|
||||
.sst = r570_dp_sst,
|
||||
.vcpi = r570_dp_vcpi,
|
||||
},
|
||||
.chan = {
|
||||
.set_pushbuf = r570_disp_chan_set_pushbuf,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@ r570_gsp_xlat_mc_engine_idx(u32 mc_engine_idx, enum nvkm_subdev_type *ptype, int
|
|||
*ptype = NVKM_ENGINE_DISP;
|
||||
*pinst = 0;
|
||||
return true;
|
||||
case MC_ENGINE_IDX_DISP_LOW:
|
||||
/* GB20x+ report a separate low-latency display vector, used
|
||||
* for head-timing interrupts. Expose it as a second DISP
|
||||
* interrupt instance. r535_disp_oneinit() attaches the
|
||||
* handler to it when the chip's gsp.intr_low_latency is set.
|
||||
*/
|
||||
*ptype = NVKM_ENGINE_DISP;
|
||||
*pinst = 1;
|
||||
return true;
|
||||
case MC_ENGINE_IDX_CE0 ... MC_ENGINE_IDX_CE19:
|
||||
*ptype = NVKM_ENGINE_CE;
|
||||
*pinst = mc_engine_idx - MC_ENGINE_IDX_CE0;
|
||||
|
|
|
|||
|
|
@ -256,6 +256,8 @@ typedef struct NV0073_CTRL_DP_CTRL_PARAMS {
|
|||
NvU32 eightLaneDpcdBaseAddr;
|
||||
} NV0073_CTRL_DP_CTRL_PARAMS;
|
||||
|
||||
#define NV0073_CTRL_CMD_DP_CONFIG_STREAM (0x731362U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_DP_INTERFACE_ID << 8) | NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS_MESSAGE_ID" */
|
||||
|
||||
typedef struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS {
|
||||
NvU32 subDeviceInstance;
|
||||
NvU32 head;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#ifndef __NVKM_RM_H__
|
||||
#define __NVKM_RM_H__
|
||||
#include "handles.h"
|
||||
struct nvkm_ior;
|
||||
struct nvkm_outp;
|
||||
struct r535_gr;
|
||||
|
||||
|
|
@ -93,6 +94,10 @@ struct nvkm_rm_api {
|
|||
struct {
|
||||
int (*get_caps)(struct nvkm_disp *, int *link_bw, bool *mst, bool *wm);
|
||||
int (*set_indexed_link_rates)(struct nvkm_outp *);
|
||||
int (*sst)(struct nvkm_ior *, int head, bool ef,
|
||||
u32 watermark, u32 hblanksym, u32 vblanksym);
|
||||
int (*vcpi)(struct nvkm_ior *, int head,
|
||||
u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn);
|
||||
} dp;
|
||||
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -195,6 +195,9 @@ check_io_mapping(struct nv50_instmem *imem)
|
|||
{
|
||||
struct nvkm_device *device = imem->base.subdev.device;
|
||||
|
||||
if (imem->iomap.size)
|
||||
return true;
|
||||
|
||||
return io_mapping_init_wc(&imem->iomap,
|
||||
device->func->resource_addr(device, NVKM_BAR2_INST),
|
||||
device->func->resource_size(device, NVKM_BAR2_INST)) != NULL;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <linux/aperture.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
|
|
@ -238,7 +239,7 @@ static bool is_avivo(u32 vendor, u32 device)
|
|||
/* This will match most R5xx */
|
||||
return (vendor == PCI_VENDOR_ID_ATI) &&
|
||||
((device >= PCI_VENDOR_ID_ATI_R520 && device < 0x7800) ||
|
||||
(PCI_VENDOR_ID_ATI_R600 >= 0x9400));
|
||||
(device >= PCI_VENDOR_ID_ATI_R600));
|
||||
}
|
||||
|
||||
static enum ofdrm_model display_get_model_of(struct drm_device *dev, struct device_node *of_node)
|
||||
|
|
@ -913,7 +914,10 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv,
|
|||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
fb_size = linebytes * height;
|
||||
if (check_mul_overflow(linebytes, height, &fb_size)) {
|
||||
drm_err(dev, "framebuffer size exceeds maximum\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to figure out the address of the framebuffer. Unfortunately, Open
|
||||
|
|
|
|||
|
|
@ -904,6 +904,7 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm,
|
|||
struct tegra_dc *dc)
|
||||
{
|
||||
unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm);
|
||||
unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE);
|
||||
enum drm_plane_type type = DRM_PLANE_TYPE_PRIMARY;
|
||||
struct tegra_plane *plane;
|
||||
unsigned int num_formats;
|
||||
|
|
@ -939,6 +940,7 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm,
|
|||
}
|
||||
|
||||
drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs);
|
||||
drm_plane_create_blend_mode_property(&plane->base, blend_caps);
|
||||
drm_plane_create_zpos_property(&plane->base, plane->index, 0, 255);
|
||||
|
||||
err = drm_plane_create_rotation_property(&plane->base,
|
||||
|
|
@ -1209,6 +1211,7 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
|
|||
struct tegra_dc *dc)
|
||||
{
|
||||
unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm);
|
||||
unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE);
|
||||
struct tegra_plane *plane;
|
||||
unsigned int num_formats;
|
||||
const u32 *formats;
|
||||
|
|
@ -1252,6 +1255,7 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
|
|||
}
|
||||
|
||||
drm_plane_helper_add(&plane->base, &tegra_cursor_plane_helper_funcs);
|
||||
drm_plane_create_blend_mode_property(&plane->base, blend_caps);
|
||||
drm_plane_create_zpos_immutable_property(&plane->base, 255);
|
||||
|
||||
return &plane->base;
|
||||
|
|
@ -1356,6 +1360,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
|
|||
bool cursor)
|
||||
{
|
||||
unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm);
|
||||
unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE);
|
||||
struct tegra_plane *plane;
|
||||
unsigned int num_formats;
|
||||
enum drm_plane_type type;
|
||||
|
|
@ -1394,6 +1399,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
|
|||
}
|
||||
|
||||
drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs);
|
||||
drm_plane_create_blend_mode_property(&plane->base, blend_caps);
|
||||
drm_plane_create_zpos_property(&plane->base, plane->index, 0, 255);
|
||||
|
||||
err = drm_plane_create_rotation_property(&plane->base,
|
||||
|
|
|
|||
|
|
@ -759,6 +759,7 @@ struct drm_plane *tegra_shared_plane_create(struct drm_device *drm,
|
|||
unsigned int index,
|
||||
enum drm_plane_type type)
|
||||
{
|
||||
unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE);
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
struct tegra_display_hub *hub = tegra->hub;
|
||||
struct tegra_shared_plane *plane;
|
||||
|
|
@ -797,6 +798,7 @@ struct drm_plane *tegra_shared_plane_create(struct drm_device *drm,
|
|||
}
|
||||
|
||||
drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs);
|
||||
drm_plane_create_blend_mode_property(p, blend_caps);
|
||||
drm_plane_create_zpos_property(p, 0, 0, 255);
|
||||
|
||||
return p;
|
||||
|
|
|
|||
|
|
@ -582,6 +582,9 @@ static int cirrus_pci_probe(struct pci_dev *pdev,
|
|||
struct cirrus_device *cirrus;
|
||||
int ret;
|
||||
|
||||
if (pci_resource_len(pdev, 0) < CIRRUS_VRAM_SIZE)
|
||||
return -ENODEV;
|
||||
|
||||
ret = aperture_remove_conflicting_pci_devices(pdev, cirrus_driver.name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ virtio_gpu_user_framebuffer_create(struct drm_device *dev,
|
|||
if (ret) {
|
||||
kfree(virtio_gpu_fb);
|
||||
drm_gem_object_put(obj);
|
||||
return NULL;
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return &virtio_gpu_fb->base;
|
||||
|
|
@ -378,8 +378,11 @@ int virtio_gpu_modeset_init(struct virtio_gpu_device *vgdev)
|
|||
|
||||
vgdev->ddev->mode_config.fb_modifiers_not_supported = true;
|
||||
|
||||
for (i = 0 ; i < vgdev->num_scanouts; ++i)
|
||||
vgdev_output_init(vgdev, i);
|
||||
for (i = 0; i < vgdev->num_scanouts; ++i) {
|
||||
ret = vgdev_output_init(vgdev, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = drm_vblank_init(vgdev->ddev, vgdev->num_scanouts);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@
|
|||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/virtgpu_drm.h>
|
||||
|
||||
#include <xen/xen.h>
|
||||
|
||||
#define DRIVER_NAME "virtio_gpu"
|
||||
#define DRIVER_DESC "virtio GPU"
|
||||
|
||||
|
|
@ -60,6 +62,24 @@
|
|||
/* See virtio_gpu_ctx_create. One additional character for NULL terminator. */
|
||||
#define DEBUG_NAME_MAX_LEN 65
|
||||
|
||||
/*
|
||||
* Whether the host must be told about resource backing pages by DMA address
|
||||
* rather than guest-physical address.
|
||||
*
|
||||
* This mirrors vring_use_map_api() in drivers/virtio/virtio_ring.c, including
|
||||
* its xen_domain() case.
|
||||
*/
|
||||
static inline bool virtio_gpu_use_dma_api(const struct virtio_device *vdev)
|
||||
{
|
||||
if (!virtio_has_dma_quirk(vdev))
|
||||
return true;
|
||||
|
||||
if (xen_domain())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct virtio_gpu_object_params {
|
||||
unsigned long size;
|
||||
bool dumb;
|
||||
|
|
@ -343,6 +363,7 @@ void virtio_gpu_array_put_free_work(struct work_struct *work);
|
|||
/* virtgpu_vq.c */
|
||||
int virtio_gpu_alloc_vbufs(struct virtio_gpu_device *vgdev);
|
||||
void virtio_gpu_free_vbufs(struct virtio_gpu_device *vgdev);
|
||||
void virtio_gpu_reclaim_vbufs(struct virtio_gpu_device *vgdev);
|
||||
void virtio_gpu_cmd_create_resource(struct virtio_gpu_device *vgdev,
|
||||
struct virtio_gpu_object *bo,
|
||||
struct virtio_gpu_object_params *params,
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ void virtio_gpu_deinit(struct drm_device *dev)
|
|||
flush_work(&vgdev->cursorq.dequeue_work);
|
||||
flush_work(&vgdev->config_changed_work);
|
||||
virtio_reset_device(vgdev->vdev);
|
||||
virtio_gpu_reclaim_vbufs(vgdev);
|
||||
vgdev->vdev->config->del_vqs(vgdev->vdev);
|
||||
mutex_destroy(&vgdev->obj_restore_lock);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ static int virtio_gpu_object_shmem_init(struct virtio_gpu_device *vgdev,
|
|||
struct virtio_gpu_mem_entry **ents,
|
||||
unsigned int *nents)
|
||||
{
|
||||
bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev);
|
||||
bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
|
||||
struct scatterlist *sg;
|
||||
struct sg_table *pages;
|
||||
int si;
|
||||
|
|
|
|||
|
|
@ -208,6 +208,21 @@ static void free_vbuf(struct virtio_gpu_device *vgdev,
|
|||
kmem_cache_free(vgdev->vbufs, vbuf);
|
||||
}
|
||||
|
||||
void virtio_gpu_reclaim_vbufs(struct virtio_gpu_device *vgdev)
|
||||
{
|
||||
struct virtio_gpu_vbuffer *vbuf;
|
||||
|
||||
while ((vbuf = virtqueue_detach_unused_buf(vgdev->ctrlq.vq))) {
|
||||
if (vbuf->objs)
|
||||
virtio_gpu_array_put_free(vbuf->objs);
|
||||
if (vbuf->resp_cb_data)
|
||||
virtio_gpu_cleanup_object(vbuf->resp_cb_data);
|
||||
free_vbuf(vgdev, vbuf);
|
||||
}
|
||||
while ((vbuf = virtqueue_detach_unused_buf(vgdev->cursorq.vq)))
|
||||
free_vbuf(vgdev, vbuf);
|
||||
}
|
||||
|
||||
static void reclaim_vbufs(struct virtqueue *vq, struct list_head *reclaim_list)
|
||||
{
|
||||
struct virtio_gpu_vbuffer *vbuf;
|
||||
|
|
@ -764,7 +779,7 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
|
|||
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]);
|
||||
struct virtio_gpu_transfer_to_host_2d *cmd_p;
|
||||
struct virtio_gpu_vbuffer *vbuf;
|
||||
bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev);
|
||||
bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
|
||||
|
||||
if (virtio_gpu_is_shmem(bo) && use_dma_api)
|
||||
dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
|
||||
|
|
@ -795,7 +810,7 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
|
|||
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]);
|
||||
struct virtio_gpu_transfer_to_host_2d *cmd_p;
|
||||
struct virtio_gpu_vbuffer *vbuf;
|
||||
bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev);
|
||||
bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
|
||||
|
||||
if (virtio_gpu_is_shmem(bo) && use_dma_api)
|
||||
dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
|
||||
|
|
@ -1228,7 +1243,7 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
|
|||
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]);
|
||||
struct virtio_gpu_transfer_host_3d *cmd_p;
|
||||
struct virtio_gpu_vbuffer *vbuf;
|
||||
bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev);
|
||||
bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
|
||||
|
||||
if (virtio_gpu_is_shmem(bo) && use_dma_api)
|
||||
dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
|
||||
|
|
|
|||
|
|
@ -1822,24 +1822,20 @@ static int fastrpc_dmabuf_alloc(struct fastrpc_user *fl, char __user *argp)
|
|||
return err;
|
||||
}
|
||||
|
||||
bp.fd = dma_buf_fd(buf->dmabuf, O_ACCMODE);
|
||||
bp.fd = get_unused_fd_flags(O_ACCMODE);
|
||||
if (bp.fd < 0) {
|
||||
dma_buf_put(buf->dmabuf);
|
||||
return -EINVAL;
|
||||
return bp.fd;
|
||||
}
|
||||
|
||||
if (copy_to_user(argp, &bp, sizeof(bp))) {
|
||||
/*
|
||||
* The usercopy failed, but we can't do much about it, as
|
||||
* dma_buf_fd() already called fd_install() and made the
|
||||
* file descriptor accessible for the current process. It
|
||||
* might already be closed and dmabuf no longer valid when
|
||||
* we reach this point. Therefore "leak" the fd and rely on
|
||||
* the process exit path to do any required cleanup.
|
||||
*/
|
||||
put_unused_fd(bp.fd);
|
||||
dma_buf_put(buf->dmabuf);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
dma_buf_fd_install(buf->dmabuf, bp.fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#ifndef _DRM_PAGEMAP_H_
|
||||
#define _DRM_PAGEMAP_H_
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/dma-direction.h>
|
||||
#include <linux/hmm.h>
|
||||
#include <linux/memremap.h>
|
||||
|
|
@ -339,6 +340,9 @@ struct drm_pagemap_migrate_details {
|
|||
|
||||
#if IS_ENABLED(CONFIG_ZONE_DEVICE)
|
||||
|
||||
#define DRM_PAGEMAP_ZDD_FLAG_MIGRATED BIT(0)
|
||||
#define DRM_PAGEMAP_ZDD_FLAG_MASK DRM_PAGEMAP_ZDD_FLAG_MIGRATED
|
||||
|
||||
int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation,
|
||||
struct mm_struct *mm,
|
||||
unsigned long start, unsigned long end,
|
||||
|
|
@ -373,7 +377,9 @@ static inline struct drm_pagemap_zdd *drm_pagemap_page_zone_device_data(struct p
|
|||
{
|
||||
struct folio *folio = page_folio(page);
|
||||
|
||||
return folio_zone_device_data(folio);
|
||||
return (struct drm_pagemap_zdd *)
|
||||
((unsigned long)folio_zone_device_data(folio) &
|
||||
~DRM_PAGEMAP_ZDD_FLAG_MASK);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -567,6 +567,7 @@ void dma_buf_unpin(struct dma_buf_attachment *attach);
|
|||
struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info);
|
||||
|
||||
int dma_buf_fd(struct dma_buf *dmabuf, int flags);
|
||||
void dma_buf_fd_install(struct dma_buf *dmabuf, int fd);
|
||||
struct dma_buf *dma_buf_get(int fd);
|
||||
void dma_buf_put(struct dma_buf *dmabuf);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ struct dma_fence_array_cb {
|
|||
/**
|
||||
* struct dma_fence_array - fence to represent an array of fences
|
||||
* @base: fence base class
|
||||
* @lock: spinlock for fence handling
|
||||
* @num_fences: number of fences in the array
|
||||
* @num_pending: fences in the array still pending
|
||||
* @fences: array of the fences
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
* @prev: previous fence of the chain
|
||||
* @prev_seqno: original previous seqno before garbage collection
|
||||
* @fence: encapsulated fence
|
||||
* @lock: spinlock for fence handling
|
||||
*/
|
||||
struct dma_fence_chain {
|
||||
struct dma_fence base;
|
||||
|
|
@ -81,9 +80,8 @@ dma_fence_chain_contained(struct dma_fence *fence)
|
|||
}
|
||||
|
||||
/**
|
||||
* dma_fence_chain_alloc
|
||||
*
|
||||
* Returns a new struct dma_fence_chain object or NULL on failure.
|
||||
* dma_fence_chain_alloc - Returns a new &struct dma_fence_chain object or
|
||||
* %NULL on failure.
|
||||
*
|
||||
* This specialized allocator has to be a macro for its allocations to be
|
||||
* accounted separately (to have a separate alloc_tag). The typecast is
|
||||
|
|
@ -93,7 +91,8 @@ dma_fence_chain_contained(struct dma_fence *fence)
|
|||
kmalloc_obj(struct dma_fence_chain)
|
||||
|
||||
/**
|
||||
* dma_fence_chain_free
|
||||
* dma_fence_chain_free - Frees an allocated but not used
|
||||
* &struct dma_fence_chain object.
|
||||
* @chain: chain node to free
|
||||
*
|
||||
* Frees up an allocated but not used struct dma_fence_chain object. This
|
||||
|
|
|
|||
|
|
@ -390,6 +390,116 @@ static void test_alloc_errors(char *heap_name)
|
|||
close(heap_fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* count_open_fds - return the number of open file descriptors.
|
||||
*
|
||||
* The fd opened by opendir() itself is counted, but since it is opened
|
||||
* and closed within each call, it cancels out when comparing two counts.
|
||||
* Returns -1 on error.
|
||||
*/
|
||||
static int count_open_fds(void)
|
||||
{
|
||||
DIR *d = opendir("/proc/self/fd");
|
||||
struct dirent *de;
|
||||
int count = 0;
|
||||
|
||||
if (!d)
|
||||
return -1;
|
||||
|
||||
while ((de = readdir(d)))
|
||||
if (de->d_name[0] != '.')
|
||||
count++;
|
||||
closedir(d);
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* test_alloc_no_fd_leak_on_efault - verify no fd is leaked when
|
||||
* copy_to_user() fails during DMA_HEAP_IOCTL_ALLOC.
|
||||
*
|
||||
* The bug: dma_buf_fd() called fd_install() before copy_to_user().
|
||||
* If copy_to_user() then failed (e.g. via mprotect), the fd was
|
||||
* silently installed in the fd table but never returned to userspace.
|
||||
*
|
||||
* The fix: reserve the fd with get_unused_fd_flags() first, attempt
|
||||
* copy_to_user(), and only call fd_install() on success.
|
||||
*
|
||||
* We trigger the failure by placing the ioctl argument in a private
|
||||
* anonymous page and flipping it to PROT_READ before the ioctl.
|
||||
* Inside the kernel, copy_from_user() reads from the page (reads are
|
||||
* allowed under PROT_READ, so it succeeds), but copy_to_user() that
|
||||
* writes the fd number back faults, returning -EFAULT. We then
|
||||
* count open file descriptors before and after; with the bug an extra
|
||||
* fd is left in the table.
|
||||
*/
|
||||
static void test_alloc_no_fd_leak_on_efault(char *heap_name)
|
||||
{
|
||||
int heap_fd = -1;
|
||||
int fd_before, fd_after;
|
||||
int ret;
|
||||
long page_size;
|
||||
struct dma_heap_allocation_data *req;
|
||||
|
||||
ksft_print_msg("Testing fd leak when copy_to_user() fails:\n");
|
||||
|
||||
heap_fd = dmabuf_heap_open(heap_name);
|
||||
|
||||
page_size = sysconf(_SC_PAGESIZE);
|
||||
|
||||
/*
|
||||
* Place the ioctl argument in its own private anonymous page so
|
||||
* we can flip its protection independently.
|
||||
*/
|
||||
req = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (req == MAP_FAILED) {
|
||||
ksft_test_result_fail("mmap failed: %s\n", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
memset(req, 0, sizeof(*req));
|
||||
req->len = page_size;
|
||||
req->fd_flags = O_RDWR | O_CLOEXEC;
|
||||
|
||||
fd_before = count_open_fds();
|
||||
if (fd_before < 0) {
|
||||
ksft_test_result_fail("count_open_fds: %s\n", strerror(errno));
|
||||
munmap(req, page_size);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make the page read-only so copy_to_user() will fault. The
|
||||
* ioctl must fail with -1; if it returns success the test setup
|
||||
* is broken (mprotect is synchronous, so there is no race).
|
||||
*/
|
||||
mprotect(req, page_size, PROT_READ);
|
||||
|
||||
ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, req);
|
||||
|
||||
/* Re-allow writes so munmap can clean up */
|
||||
mprotect(req, page_size, PROT_READ | PROT_WRITE);
|
||||
munmap(req, page_size);
|
||||
|
||||
if (ret != -1) {
|
||||
ksft_test_result_fail("ioctl returned %d, expected -1 EFAULT\n",
|
||||
ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_after = count_open_fds();
|
||||
if (fd_after < 0) {
|
||||
ksft_test_result_fail("count_open_fds: %s\n", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
ksft_test_result(fd_before == fd_after,
|
||||
"fd leak on EFAULT: before=%d after=%d\n",
|
||||
fd_before, fd_after);
|
||||
out:
|
||||
close(heap_fd);
|
||||
}
|
||||
|
||||
static int numer_of_heaps(void)
|
||||
{
|
||||
DIR *d = opendir(DEVPATH);
|
||||
|
|
@ -420,7 +530,7 @@ int main(void)
|
|||
return KSFT_SKIP;
|
||||
}
|
||||
|
||||
ksft_set_plan(11 * numer_of_heaps());
|
||||
ksft_set_plan(12 * numer_of_heaps());
|
||||
|
||||
while ((dir = readdir(d))) {
|
||||
if (!strncmp(dir->d_name, ".", 2))
|
||||
|
|
@ -435,6 +545,7 @@ int main(void)
|
|||
test_alloc_zeroed(dir->d_name, ONE_MEG);
|
||||
test_alloc_compat(dir->d_name);
|
||||
test_alloc_errors(dir->d_name);
|
||||
test_alloc_no_fd_leak_on_efault(dir->d_name);
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user