drm-misc-next for v5.15:
UAPI Changes: Cross-subsystem Changes: - udmabuf: Add support for mapping hugepages - Add dma-buf stats to sysfs. - Assorted fixes to fbdev/omap2. - dma-buf: Document DMA_BUF_IOCTL_SYNC - Improve dma-buf non-dynamic exporter expectations better. - Add module parameters for dma-buf size and list limit. - Add HDMI codec support to vc4, to replace vc4's own codec. - Document dma-buf implicit fencing rules. - dma_resv_test_signaled test_all handling. Core Changes: - Extract i915's eDP backlight code into DRM helpers. - Assorted docbook updates. - Rework drm_dp_aux documentation. - Add support for the DP aux bus. - Shrink dma-fence-chain slightly. - Add alloc/free helpers for dma-fence-chain. - Assorted fixes to TTM., drm/of, bridge - drm_gem_plane_helper_prepare/cleanup_fb is now the default for gem drivers. - Small fix for scheduler completion. - Remove use of drm_device.irq_enabled. - Print the driver name to dmesg when registering framebuffer. - Export drm/gem's shadow plane handling, and use it in vkms. - Assorted small fixes. Driver Changes: - Add eDP backlight to nouveau. - Assorted fixes and cleanups to nouveau, panfrost, vmwgfx, anx7625, amdgpu, gma500, radeon, mgag200, vgem, vc4, vkms, omapdrm. - Add support for Samsung DB7430, Samsung ATNA33XC20, EDT ETMV570G2DHU, EDT ETM0350G0DH6, Innolux EJ030NA panels. - Fix some simple pannels missing bus_format and connector types. - Add mks-guest-stats instrumentation support to vmwgfx. - Merge i915-ttm topic branch. - Make s6e63m0 panel use Mipi-DBI helpers. - Add detect() supoprt for AST. - Use interrupts for hotplug on vc4. - vmwgfx is now moved to drm-misc-next, as sroland is no longer a maintainer for now. - vmwgfx now uses copies of vmware's internal device headers. - Slowly convert ti-sn65dsi83 over to atomic. - Rework amdgpu dma-resv handling. - Fix virtio fencing for planes. - Ensure amdgpu can always evict to SYSTEM. - Many drivers fixed for implicit fencing rules. - Set default prepare/cleanup fb for tiny, vram and simple helpers too. - Rework panfrost gpu reset and related serialization. - Update VKMS todo list. - Make bochs a tiny gpu driver, and use vram helper. - Use linux irq interfaces instead of drm_irq in some drivers. - Add support for Raspberry Pi Pico to GUD. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEuXvWqAysSYEJGuVH/lWMcqZwE8MFAmDxaBwACgkQ/lWMcqZw E8PBYRAAsZgmuQU1urEsDTL931jWoJ8zxHpxSLow8ZtplembyhloGeRXRmGT8erd ocw1wAzm0UajbFLvv50XW5N4jPnsn9IBRQVhfNNc06g4OH6qy17PPAA+clHaBJrf BFiAcK4rzmUet3+6335ko/OvkD5er0s7ipNljxgB7FkIwP3gh3NEFG0yFcpFpxF4 fzT5Wz5vMW++XUCXZHMX+vBMjFP2AosxLVvsnxpM/48dyFWTiYRg7jhy5bICKYBM 3GdRj2e1wm3cAsZISbqtDpXSlstIw6u0w+BB6ryQvD/K5nPTqydE/YMOB85DUWLg Sp1tijxM/KtOyC5w/IpDLkf9X24KAIcu0eKffUGbkLvIkP5cSyibelOtZBG6Jmln AubXpgi4+mGVyYvMEVngHyrY2tW/rtpNGr/g9To9hYVHKkdRZUtolQk7KgtdV7v3 pFq60AilYTENJthkjCRoTi66BsocpaJfQOyppp6uD8/a0Spxfrq5tM+POWNylqxB 70L2ObvM4Xx51GI0ziCZQwkMp2Uzwosr+6CdbrzQKaxxpbQEcr3frkv6cap5V0WY lnYgFw3dbA/Ga6YsnInQ87KmF4svnaWB2z/KzfnBF5pNrwoR9/4K5k7Vfb3P9YyN w+nrfeHto0r768PjC/05uyD9diDuHOw3RHtljf/C4klBNRDDovU= =x8Eo -----END PGP SIGNATURE----- Merge tag 'drm-misc-next-2021-07-16' of git://anongit.freedesktop.org/drm/drm-misc into drm-next drm-misc-next for v5.15: UAPI Changes: Cross-subsystem Changes: - udmabuf: Add support for mapping hugepages - Add dma-buf stats to sysfs. - Assorted fixes to fbdev/omap2. - dma-buf: Document DMA_BUF_IOCTL_SYNC - Improve dma-buf non-dynamic exporter expectations better. - Add module parameters for dma-buf size and list limit. - Add HDMI codec support to vc4, to replace vc4's own codec. - Document dma-buf implicit fencing rules. - dma_resv_test_signaled test_all handling. Core Changes: - Extract i915's eDP backlight code into DRM helpers. - Assorted docbook updates. - Rework drm_dp_aux documentation. - Add support for the DP aux bus. - Shrink dma-fence-chain slightly. - Add alloc/free helpers for dma-fence-chain. - Assorted fixes to TTM., drm/of, bridge - drm_gem_plane_helper_prepare/cleanup_fb is now the default for gem drivers. - Small fix for scheduler completion. - Remove use of drm_device.irq_enabled. - Print the driver name to dmesg when registering framebuffer. - Export drm/gem's shadow plane handling, and use it in vkms. - Assorted small fixes. Driver Changes: - Add eDP backlight to nouveau. - Assorted fixes and cleanups to nouveau, panfrost, vmwgfx, anx7625, amdgpu, gma500, radeon, mgag200, vgem, vc4, vkms, omapdrm. - Add support for Samsung DB7430, Samsung ATNA33XC20, EDT ETMV570G2DHU, EDT ETM0350G0DH6, Innolux EJ030NA panels. - Fix some simple pannels missing bus_format and connector types. - Add mks-guest-stats instrumentation support to vmwgfx. - Merge i915-ttm topic branch. - Make s6e63m0 panel use Mipi-DBI helpers. - Add detect() supoprt for AST. - Use interrupts for hotplug on vc4. - vmwgfx is now moved to drm-misc-next, as sroland is no longer a maintainer for now. - vmwgfx now uses copies of vmware's internal device headers. - Slowly convert ti-sn65dsi83 over to atomic. - Rework amdgpu dma-resv handling. - Fix virtio fencing for planes. - Ensure amdgpu can always evict to SYSTEM. - Many drivers fixed for implicit fencing rules. - Set default prepare/cleanup fb for tiny, vram and simple helpers too. - Rework panfrost gpu reset and related serialization. - Update VKMS todo list. - Make bochs a tiny gpu driver, and use vram helper. - Use linux irq interfaces instead of drm_irq in some drivers. - Add support for Raspberry Pi Pico to GUD. Signed-off-by: Dave Airlie <airlied@redhat.com> # gpg: Signature made Fri 16 Jul 2021 21:06:04 AEST # gpg: using RSA key B97BD6A80CAC4981091AE547FE558C72A67013C3 # gpg: Good signature from "Maarten Lankhorst <maarten.lankhorst@linux.intel.com>" [expired] # gpg: aka "Maarten Lankhorst <maarten@debian.org>" [expired] # gpg: aka "Maarten Lankhorst <maarten.lankhorst@canonical.com>" [expired] # gpg: Note: This key has expired! # Primary key fingerprint: B97B D6A8 0CAC 4981 091A E547 FE55 8C72 A670 13C3 From: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/444811c3-cbec-e9d5-9a6b-9632eda7962a@linux.intel.com
This commit is contained in:
		
						commit
						588b3eee52
					
				
							
								
								
									
										52
									
								
								Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| What:		/sys/kernel/dmabuf/buffers | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	The /sys/kernel/dmabuf/buffers directory contains a | ||||
| 		snapshot of the internal state of every DMA-BUF. | ||||
| 		/sys/kernel/dmabuf/buffers/<inode_number> will contain the | ||||
| 		statistics for the DMA-BUF with the unique inode number | ||||
| 		<inode_number> | ||||
| Users:		kernel memory tuning/debugging tools | ||||
| 
 | ||||
| What:		/sys/kernel/dmabuf/buffers/<inode_number>/exporter_name | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	This file is read-only and contains the name of the exporter of | ||||
| 		the DMA-BUF. | ||||
| 
 | ||||
| What:		/sys/kernel/dmabuf/buffers/<inode_number>/size | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	This file is read-only and specifies the size of the DMA-BUF in | ||||
| 		bytes. | ||||
| 
 | ||||
| What:		/sys/kernel/dmabuf/buffers/<inode_number>/attachments | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	This directory will contain subdirectories representing every | ||||
| 		attachment of the DMA-BUF. | ||||
| 
 | ||||
| What:		/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attachment_uid> | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	This directory will contain information on the attached device | ||||
| 		and the number of current distinct device mappings. | ||||
| 
 | ||||
| What:		/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attachment_uid>/device | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	This file is read-only and is a symlink to the attached device's | ||||
| 		sysfs entry. | ||||
| 
 | ||||
| What:		/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attachment_uid>/map_counter | ||||
| Date:		May 2021 | ||||
| KernelVersion:	v5.13 | ||||
| Contact:	Hridya Valsaraju <hridya@google.com> | ||||
| Description:	This file is read-only and contains a map_counter indicating the | ||||
| 		number of distinct device mappings of the attachment. | ||||
| @ -70,6 +70,9 @@ properties: | ||||
|     const: 1 | ||||
|     description: See ../../pwm/pwm.yaml for description of the cell formats. | ||||
| 
 | ||||
|   aux-bus: | ||||
|     $ref: /schemas/display/dp-aux-bus.yaml# | ||||
| 
 | ||||
|   ports: | ||||
|     $ref: /schemas/graph.yaml#/properties/ports | ||||
| 
 | ||||
| @ -150,7 +153,6 @@ properties: | ||||
| required: | ||||
|   - compatible | ||||
|   - reg | ||||
|   - enable-gpios | ||||
|   - vccio-supply | ||||
|   - vpll-supply | ||||
|   - vcca-supply | ||||
| @ -201,11 +203,26 @@ examples: | ||||
| 
 | ||||
|           port@1 { | ||||
|             reg = <1>; | ||||
|             endpoint { | ||||
|             sn65dsi86_out: endpoint { | ||||
|               remote-endpoint = <&panel_in_edp>; | ||||
|             }; | ||||
|           }; | ||||
|         }; | ||||
| 
 | ||||
|         aux-bus { | ||||
|           panel { | ||||
|             compatible = "boe,nv133fhm-n62"; | ||||
|             power-supply = <&pp3300_dx_edp>; | ||||
|             backlight = <&backlight>; | ||||
|             hpd-gpios = <&sn65dsi86_bridge 2 GPIO_ACTIVE_HIGH>; | ||||
| 
 | ||||
|             port { | ||||
|               panel_in_edp: endpoint { | ||||
|                 remote-endpoint = <&sn65dsi86_out>; | ||||
|               }; | ||||
|             }; | ||||
|           }; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|   - | | ||||
|  | ||||
							
								
								
									
										37
									
								
								Documentation/devicetree/bindings/display/dp-aux-bus.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Documentation/devicetree/bindings/display/dp-aux-bus.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) | ||||
| %YAML 1.2 | ||||
| --- | ||||
| $id: http://devicetree.org/schemas/display/dp-aux-bus.yaml# | ||||
| $schema: http://devicetree.org/meta-schemas/core.yaml# | ||||
| 
 | ||||
| title: DisplayPort AUX bus | ||||
| 
 | ||||
| maintainers: | ||||
|   - Douglas Anderson <dianders@chromium.org> | ||||
| 
 | ||||
| description: | ||||
|   DisplayPort controllers provide a control channel to the sinks that | ||||
|   are hooked up to them. This is the DP AUX bus. Over the DP AUX bus | ||||
|   we can query properties about a sink and also configure it. In | ||||
|   particular, DP sinks support DDC over DP AUX which allows tunneling | ||||
|   a standard I2C DDC connection over the AUX channel. | ||||
| 
 | ||||
|   To model this relationship, DP sinks should be placed as children | ||||
|   of the DP controller under the "aux-bus" node. | ||||
| 
 | ||||
|   At the moment, this binding only handles the eDP case. It is | ||||
|   possible it will be extended in the future to handle the DP case. | ||||
|   For DP, presumably a connector would be listed under the DP AUX | ||||
|   bus instead of a panel. | ||||
| 
 | ||||
| properties: | ||||
|   $nodename: | ||||
|     const: "aux-bus" | ||||
| 
 | ||||
|   panel: | ||||
|     $ref: panel/panel-common.yaml# | ||||
| 
 | ||||
| additionalProperties: false | ||||
| 
 | ||||
| required: | ||||
|   - panel | ||||
| @ -0,0 +1,62 @@ | ||||
| # SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) | ||||
| %YAML 1.2 | ||||
| --- | ||||
| $id: http://devicetree.org/schemas/display/panel/innolux,ej030na.yaml# | ||||
| $schema: http://devicetree.org/meta-schemas/core.yaml# | ||||
| 
 | ||||
| title: Innolux EJ030NA 3.0" (320x480 pixels) 24-bit TFT LCD panel | ||||
| 
 | ||||
| description: | | ||||
|   The panel must obey the rules for a SPI slave device as specified in | ||||
|   spi/spi-controller.yaml | ||||
| 
 | ||||
| maintainers: | ||||
|   - Paul Cercueil <paul@crapouillou.net> | ||||
| 
 | ||||
| allOf: | ||||
|   - $ref: panel-common.yaml# | ||||
| 
 | ||||
| properties: | ||||
|   compatible: | ||||
|     const: innolux,ej030na | ||||
| 
 | ||||
|   backlight: true | ||||
|   port: true | ||||
|   power-supply: true | ||||
|   reg: true | ||||
|   reset-gpios: true | ||||
| 
 | ||||
| required: | ||||
|   - compatible | ||||
|   - reg | ||||
|   - power-supply | ||||
|   - reset-gpios | ||||
| 
 | ||||
| unevaluatedProperties: false | ||||
| 
 | ||||
| examples: | ||||
|   - | | ||||
|     #include <dt-bindings/gpio/gpio.h> | ||||
| 
 | ||||
|     spi { | ||||
|         #address-cells = <1>; | ||||
|         #size-cells = <0>; | ||||
| 
 | ||||
|         panel@0 { | ||||
|             compatible = "innolux,ej030na"; | ||||
|             reg = <0>; | ||||
| 
 | ||||
|             spi-max-frequency = <10000000>; | ||||
| 
 | ||||
|             reset-gpios = <&gpe 4 GPIO_ACTIVE_LOW>; | ||||
|             power-supply = <&lcd_power>; | ||||
| 
 | ||||
|             backlight = <&backlight>; | ||||
| 
 | ||||
|             port { | ||||
|                 panel_input: endpoint { | ||||
|                     remote-endpoint = <&panel_output>; | ||||
|                 }; | ||||
|             }; | ||||
|         }; | ||||
|     }; | ||||
| @ -110,6 +110,9 @@ properties: | ||||
|         # Emerging Display Technology Corp. 5.7" VGA TFT LCD panel | ||||
|       - edt,et057090dhu | ||||
|       - edt,et070080dh6 | ||||
|         # Emerging Display Technology Corp. 3.5" WVGA TFT LCD panel with | ||||
|         # capacitive multitouch | ||||
|       - edt,etm0350g0dh6 | ||||
|         # Emerging Display Technology Corp. 480x272 TFT Display with capacitive touch | ||||
|       - edt,etm043080dh6gp | ||||
|         # Emerging Display Technology Corp. 480x272 TFT Display | ||||
| @ -128,6 +131,9 @@ properties: | ||||
|         # Emerging Display Technology Corp. WVGA TFT Display with capacitive touch | ||||
|       - edt,etm0700g0dh6 | ||||
|       - edt,etm0700g0edh6 | ||||
|         # Emerging Display Technology Corp. 5.7" VGA TFT LCD panel with | ||||
|         # capacitive touch | ||||
|       - edt,etmv570g2dhu | ||||
|         # Evervision Electronics Co. Ltd. VGG804821 5.0" WVGA TFT LCD Panel | ||||
|       - evervision,vgg804821 | ||||
|         # Foxlink Group 5" WVGA TFT LCD panel | ||||
| @ -242,6 +248,8 @@ properties: | ||||
|       - rocktech,rk101ii01d-ct | ||||
|         # Rocktech Display Ltd. RK070ER9427 800(RGB)x480 TFT LCD panel | ||||
|       - rocktech,rk070er9427 | ||||
|         # Samsung 13.3" FHD (1920x1080 pixels) eDP AMOLED panel | ||||
|       - samsung,atna33xc20 | ||||
|         # Samsung 12.2" (2560x1600 pixels) TFT LCD panel | ||||
|       - samsung,lsn122dl01-c01 | ||||
|         # Samsung Electronics 10.1" WSVGA TFT LCD panel | ||||
| @ -298,6 +306,8 @@ properties: | ||||
|   enable-gpios: true | ||||
|   port: true | ||||
|   power-supply: true | ||||
|   no-hpd: true | ||||
|   hpd-gpios: true | ||||
| 
 | ||||
| additionalProperties: false | ||||
| 
 | ||||
|  | ||||
| @ -33,8 +33,11 @@ properties: | ||||
| 
 | ||||
|   backlight: true | ||||
| 
 | ||||
|   spi-cpha: true | ||||
| 
 | ||||
|   spi-cpol: true | ||||
| 
 | ||||
|   spi-max-frequency: | ||||
|     $ref: /schemas/types.yaml#/definitions/uint32 | ||||
|     description: inherited as a SPI client node, the datasheet specifies | ||||
|       maximum 300 ns minimum cycle which gives around 3 MHz max frequency | ||||
|     maximum: 3000000 | ||||
| @ -44,6 +47,9 @@ properties: | ||||
| required: | ||||
|   - compatible | ||||
|   - reg | ||||
|   - spi-cpha | ||||
|   - spi-cpol | ||||
|   - port | ||||
| 
 | ||||
| additionalProperties: false | ||||
| 
 | ||||
| @ -52,15 +58,23 @@ examples: | ||||
|     #include <dt-bindings/gpio/gpio.h> | ||||
| 
 | ||||
|     spi { | ||||
|       compatible = "spi-gpio"; | ||||
|       sck-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; | ||||
|       miso-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; | ||||
|       mosi-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>; | ||||
|       cs-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>; | ||||
|       num-chipselects = <1>; | ||||
|       #address-cells = <1>; | ||||
|       #size-cells = <0>; | ||||
|       panel@0 { | ||||
|         compatible = "samsung,lms397kf04"; | ||||
|         spi-max-frequency = <3000000>; | ||||
|         spi-cpha; | ||||
|         spi-cpol; | ||||
|         reg = <0>; | ||||
|         vci-supply = <&lcd_3v0_reg>; | ||||
|         vccio-supply = <&lcd_1v8_reg>; | ||||
|         reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; | ||||
|         reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; | ||||
|         backlight = <&ktd259>; | ||||
| 
 | ||||
|         port { | ||||
|  | ||||
| @ -88,6 +88,9 @@ consider though: | ||||
| - The DMA buffer FD is also pollable, see `Implicit Fence Poll Support`_ below for | ||||
|   details. | ||||
| 
 | ||||
| - The DMA buffer FD also supports a few dma-buf-specific ioctls, see | ||||
|   `DMA Buffer ioctls`_ below for details. | ||||
| 
 | ||||
| Basic Operation and Device DMA Access | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| @ -106,6 +109,16 @@ Implicit Fence Poll Support | ||||
| .. kernel-doc:: drivers/dma-buf/dma-buf.c | ||||
|    :doc: implicit fence polling | ||||
| 
 | ||||
| DMA-BUF statistics | ||||
| ~~~~~~~~~~~~~~~~~~ | ||||
| .. kernel-doc:: drivers/dma-buf/dma-buf-sysfs-stats.c | ||||
|    :doc: overview | ||||
| 
 | ||||
| DMA Buffer ioctls | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| .. kernel-doc:: include/uapi/linux/dma-buf.h | ||||
| 
 | ||||
| Kernel Functions and Structures Reference | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
|  | ||||
| @ -457,6 +457,19 @@ Userspace API Structures | ||||
| .. kernel-doc:: include/uapi/drm/drm_mode.h | ||||
|    :doc: overview | ||||
| 
 | ||||
| .. _crtc_index: | ||||
| 
 | ||||
| CRTC index | ||||
| ---------- | ||||
| 
 | ||||
| CRTC's have both an object ID and an index, and they are not the same thing. | ||||
| The index is used in cases where a densely packed identifier for a CRTC is | ||||
| needed, for instance a bitmask of CRTC's. The member possible_crtcs of struct | ||||
| drm_mode_get_plane is an example. | ||||
| 
 | ||||
| DRM_IOCTL_MODE_GETRESOURCES populates a structure with an array of CRTC ID's, | ||||
| and the CRTC index is its position in this array. | ||||
| 
 | ||||
| .. kernel-doc:: include/uapi/drm/drm.h | ||||
|    :internal: | ||||
| 
 | ||||
|  | ||||
| @ -98,9 +98,17 @@ with VKMS maintainers. | ||||
| IGT better support | ||||
| ------------------ | ||||
| 
 | ||||
| - Investigate: (1) test cases on kms_plane that are failing due to timeout on | ||||
|   capturing CRC; (2) when running kms_flip test cases in sequence, some | ||||
|   successful individual test cases are failing randomly. | ||||
| Debugging: | ||||
| 
 | ||||
| - kms_plane: some test cases are failing due to timeout on capturing CRC; | ||||
| 
 | ||||
| - kms_flip: when running test cases in sequence, some successful individual | ||||
|   test cases are failing randomly; when individually, some successful test | ||||
|   cases display in the log the following error:: | ||||
| 
 | ||||
|   [drm:vkms_prepare_fb [vkms]] ERROR vmap failed: -4 | ||||
| 
 | ||||
| Virtual hardware (vblank-less) mode: | ||||
| 
 | ||||
| - VKMS already has support for vblanks simulated via hrtimers, which can be | ||||
|   tested with kms_flip test; in some way, we can say that VKMS already mimics | ||||
| @ -116,7 +124,17 @@ Add Plane Features | ||||
| 
 | ||||
| There's lots of plane features we could add support for: | ||||
| 
 | ||||
| - Real overlay planes, not just cursor. | ||||
| - Multiple overlay planes. [Good to get started] | ||||
| 
 | ||||
| - Clearing primary plane: clear primary plane before plane composition (at the | ||||
|   start) for correctness of pixel blend ops. It also guarantees alpha channel | ||||
|   is cleared in the target buffer for stable crc. [Good to get started] | ||||
| 
 | ||||
| - ARGB format on primary plane: blend the primary plane into background with | ||||
|   translucent alpha. | ||||
| 
 | ||||
| - Support when the primary plane isn't exactly matching the output size: blend | ||||
|   the primary plane into the black background. | ||||
| 
 | ||||
| - Full alpha blending on all planes. | ||||
| 
 | ||||
| @ -129,13 +147,8 @@ There's lots of plane features we could add support for: | ||||
|   cursor api). | ||||
| 
 | ||||
| For all of these, we also want to review the igt test coverage and make sure | ||||
| all relevant igt testcases work on vkms. | ||||
| 
 | ||||
| Prime Buffer Sharing | ||||
| -------------------- | ||||
| 
 | ||||
| - Syzbot report - WARNING in vkms_gem_free_object: | ||||
|   https://syzkaller.appspot.com/bug?extid=e7ad70d406e74d8fc9d0 | ||||
| all relevant igt testcases work on vkms. They are good options for internship | ||||
| project. | ||||
| 
 | ||||
| Runtime Configuration | ||||
| --------------------- | ||||
| @ -153,7 +166,7 @@ module. Use/Test-cases: | ||||
|   the refresh rate. | ||||
| 
 | ||||
| The currently proposed solution is to expose vkms configuration through | ||||
| configfs.  All existing module options should be supported through configfs | ||||
| configfs. All existing module options should be supported through configfs | ||||
| too. | ||||
| 
 | ||||
| Writeback support | ||||
| @ -162,6 +175,7 @@ Writeback support | ||||
| - The writeback and CRC capture operations share the use of composer_enabled | ||||
|   boolean to ensure vblanks. Probably, when these operations work together, | ||||
|   composer_enabled needs to refcounting the composer state to proper work. | ||||
|   [Good to get started] | ||||
| 
 | ||||
| - Add support for cloned writeback outputs and related test cases using a | ||||
|   cloned output in the IGT kms_writeback. | ||||
|  | ||||
							
								
								
									
										12
									
								
								MAINTAINERS
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								MAINTAINERS
									
									
									
									
									
								
							| @ -5770,7 +5770,7 @@ M:	Gerd Hoffmann <kraxel@redhat.com> | ||||
| L:	virtualization@lists.linux-foundation.org | ||||
| S:	Maintained | ||||
| T:	git git://anongit.freedesktop.org/drm/drm-misc | ||||
| F:	drivers/gpu/drm/bochs/ | ||||
| F:	drivers/gpu/drm/tiny/bochs.c | ||||
| 
 | ||||
| DRM DRIVER FOR BOE HIMAX8279D PANELS | ||||
| M:	Jerry Han <hanxu5@huaqin.corp-partner.google.com> | ||||
| @ -5955,6 +5955,13 @@ S:	Maintained | ||||
| F:	Documentation/devicetree/bindings/display/panel/raydium,rm67191.yaml | ||||
| F:	drivers/gpu/drm/panel/panel-raydium-rm67191.c | ||||
| 
 | ||||
| DRM DRIVER FOR SAMSUNG DB7430 PANELS | ||||
| M:	Linus Walleij <linus.walleij@linaro.org> | ||||
| S:	Maintained | ||||
| T:	git git://anongit.freedesktop.org/drm/drm-misc | ||||
| F:	Documentation/devicetree/bindings/display/panel/samsung,lms397kf04.yaml | ||||
| F:	drivers/gpu/drm/panel/panel-samsung-db7430.c | ||||
| 
 | ||||
| DRM DRIVER FOR SITRONIX ST7703 PANELS | ||||
| M:	Guido Günther <agx@sigxcpu.org> | ||||
| R:	Purism Kernel Team <kernel@puri.sm> | ||||
| @ -6053,11 +6060,10 @@ F:	drivers/gpu/drm/vboxvideo/ | ||||
| 
 | ||||
| DRM DRIVER FOR VMWARE VIRTUAL GPU | ||||
| M:	"VMware Graphics" <linux-graphics-maintainer@vmware.com> | ||||
| M:	Roland Scheidegger <sroland@vmware.com> | ||||
| M:	Zack Rusin <zackr@vmware.com> | ||||
| L:	dri-devel@lists.freedesktop.org | ||||
| S:	Supported | ||||
| T:	git git://people.freedesktop.org/~sroland/linux | ||||
| T:	git git://anongit.freedesktop.org/drm/drm-misc | ||||
| F:	drivers/gpu/drm/vmwgfx/ | ||||
| F:	include/uapi/drm/vmwgfx_drm.h | ||||
| 
 | ||||
|  | ||||
| @ -255,21 +255,6 @@ | ||||
| 		#sound-dai-cells = <0>; | ||||
| 	}; | ||||
| 
 | ||||
| 	panel: panel { | ||||
| 		/* Compatible will be filled in per-board */ | ||||
| 		power-supply = <&pp3300_dx_edp>; | ||||
| 		backlight = <&backlight>; | ||||
| 		hpd-gpios = <&sn65dsi86_bridge 2 GPIO_ACTIVE_HIGH>; | ||||
| 
 | ||||
| 		ports { | ||||
| 			port { | ||||
| 				panel_in_edp: endpoint { | ||||
| 					remote-endpoint = <&sn65dsi86_out>; | ||||
| 				}; | ||||
| 			}; | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 	pwmleds { | ||||
| 		compatible = "pwm-leds"; | ||||
| 		keyboard_backlight: keyboard-backlight { | ||||
| @ -666,6 +651,21 @@ edp_brij_i2c: &i2c2 { | ||||
| 				}; | ||||
| 			}; | ||||
| 		}; | ||||
| 
 | ||||
| 		aux-bus { | ||||
| 			panel: panel { | ||||
| 				/* Compatible will be filled in per-board */ | ||||
| 				power-supply = <&pp3300_dx_edp>; | ||||
| 				backlight = <&backlight>; | ||||
| 				hpd-gpios = <&sn65dsi86_bridge 2 GPIO_ACTIVE_HIGH>; | ||||
| 
 | ||||
| 				port { | ||||
| 					panel_in_edp: endpoint { | ||||
| 						remote-endpoint = <&sn65dsi86_out>; | ||||
| 					}; | ||||
| 				}; | ||||
| 			}; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -72,6 +72,17 @@ menuconfig DMABUF_HEAPS | ||||
| 	  allows userspace to allocate dma-bufs that can be shared | ||||
| 	  between drivers. | ||||
| 
 | ||||
| menuconfig DMABUF_SYSFS_STATS | ||||
| 	bool "DMA-BUF sysfs statistics" | ||||
| 	select DMA_SHARED_BUFFER | ||||
| 	help | ||||
| 	   Choose this option to enable DMA-BUF sysfs statistics | ||||
| 	   in location /sys/kernel/dmabuf/buffers. | ||||
| 
 | ||||
| 	   /sys/kernel/dmabuf/buffers/<inode_number> will contain | ||||
| 	   statistics for the DMA-BUF with the unique inode number | ||||
| 	   <inode_number>. | ||||
| 
 | ||||
| source "drivers/dma-buf/heaps/Kconfig" | ||||
| 
 | ||||
| endmenu | ||||
|  | ||||
| @ -6,6 +6,7 @@ obj-$(CONFIG_DMABUF_HEAPS)	+= heaps/ | ||||
| obj-$(CONFIG_SYNC_FILE)		+= sync_file.o | ||||
| obj-$(CONFIG_SW_SYNC)		+= sw_sync.o sync_debug.o | ||||
| obj-$(CONFIG_UDMABUF)		+= udmabuf.o | ||||
| obj-$(CONFIG_DMABUF_SYSFS_STATS) += dma-buf-sysfs-stats.o | ||||
| 
 | ||||
| dmabuf_selftests-y := \
 | ||||
| 	selftest.o \
 | ||||
|  | ||||
							
								
								
									
										337
									
								
								drivers/dma-buf/dma-buf-sysfs-stats.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								drivers/dma-buf/dma-buf-sysfs-stats.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,337 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0-only
 | ||||
| /*
 | ||||
|  * DMA-BUF sysfs statistics. | ||||
|  * | ||||
|  * Copyright (C) 2021 Google LLC. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/dma-buf.h> | ||||
| #include <linux/dma-resv.h> | ||||
| #include <linux/kobject.h> | ||||
| #include <linux/printk.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/sysfs.h> | ||||
| 
 | ||||
| #include "dma-buf-sysfs-stats.h" | ||||
| 
 | ||||
| #define to_dma_buf_entry_from_kobj(x) container_of(x, struct dma_buf_sysfs_entry, kobj) | ||||
| 
 | ||||
| /**
 | ||||
|  * DOC: overview | ||||
|  * | ||||
|  * ``/sys/kernel/debug/dma_buf/bufinfo`` provides an overview of every DMA-BUF | ||||
|  * in the system. However, since debugfs is not safe to be mounted in | ||||
|  * production, procfs and sysfs can be used to gather DMA-BUF statistics on | ||||
|  * production systems. | ||||
|  * | ||||
|  * The ``/proc/<pid>/fdinfo/<fd>`` files in procfs can be used to gather | ||||
|  * information about DMA-BUF fds. Detailed documentation about the interface | ||||
|  * is present in Documentation/filesystems/proc.rst. | ||||
|  * | ||||
|  * Unfortunately, the existing procfs interfaces can only provide information | ||||
|  * about the DMA-BUFs for which processes hold fds or have the buffers mmapped | ||||
|  * into their address space. This necessitated the creation of the DMA-BUF sysfs | ||||
|  * statistics interface to provide per-buffer information on production systems. | ||||
|  * | ||||
|  * The interface at ``/sys/kernel/dma-buf/buffers`` exposes information about | ||||
|  * every DMA-BUF when ``CONFIG_DMABUF_SYSFS_STATS`` is enabled. | ||||
|  * | ||||
|  * The following stats are exposed by the interface: | ||||
|  * | ||||
|  * * ``/sys/kernel/dmabuf/buffers/<inode_number>/exporter_name`` | ||||
|  * * ``/sys/kernel/dmabuf/buffers/<inode_number>/size`` | ||||
|  * * ``/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attach_uid>/device`` | ||||
|  * * ``/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attach_uid>/map_counter`` | ||||
|  * | ||||
|  * The information in the interface can also be used to derive per-exporter and | ||||
|  * per-device usage statistics. The data from the interface can be gathered | ||||
|  * on error conditions or other important events to provide a snapshot of | ||||
|  * DMA-BUF usage. It can also be collected periodically by telemetry to monitor | ||||
|  * various metrics. | ||||
|  * | ||||
|  * Detailed documentation about the interface is present in | ||||
|  * Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers. | ||||
|  */ | ||||
| 
 | ||||
| struct dma_buf_stats_attribute { | ||||
| 	struct attribute attr; | ||||
| 	ssize_t (*show)(struct dma_buf *dmabuf, | ||||
| 			struct dma_buf_stats_attribute *attr, char *buf); | ||||
| }; | ||||
| #define to_dma_buf_stats_attr(x) container_of(x, struct dma_buf_stats_attribute, attr) | ||||
| 
 | ||||
| static ssize_t dma_buf_stats_attribute_show(struct kobject *kobj, | ||||
| 					    struct attribute *attr, | ||||
| 					    char *buf) | ||||
| { | ||||
| 	struct dma_buf_stats_attribute *attribute; | ||||
| 	struct dma_buf_sysfs_entry *sysfs_entry; | ||||
| 	struct dma_buf *dmabuf; | ||||
| 
 | ||||
| 	attribute = to_dma_buf_stats_attr(attr); | ||||
| 	sysfs_entry = to_dma_buf_entry_from_kobj(kobj); | ||||
| 	dmabuf = sysfs_entry->dmabuf; | ||||
| 
 | ||||
| 	if (!dmabuf || !attribute->show) | ||||
| 		return -EIO; | ||||
| 
 | ||||
| 	return attribute->show(dmabuf, attribute, buf); | ||||
| } | ||||
| 
 | ||||
| static const struct sysfs_ops dma_buf_stats_sysfs_ops = { | ||||
| 	.show = dma_buf_stats_attribute_show, | ||||
| }; | ||||
| 
 | ||||
| static ssize_t exporter_name_show(struct dma_buf *dmabuf, | ||||
| 				  struct dma_buf_stats_attribute *attr, | ||||
| 				  char *buf) | ||||
| { | ||||
| 	return sysfs_emit(buf, "%s\n", dmabuf->exp_name); | ||||
| } | ||||
| 
 | ||||
| static ssize_t size_show(struct dma_buf *dmabuf, | ||||
| 			 struct dma_buf_stats_attribute *attr, | ||||
| 			 char *buf) | ||||
| { | ||||
| 	return sysfs_emit(buf, "%zu\n", dmabuf->size); | ||||
| } | ||||
| 
 | ||||
| static struct dma_buf_stats_attribute exporter_name_attribute = | ||||
| 	__ATTR_RO(exporter_name); | ||||
| static struct dma_buf_stats_attribute size_attribute = __ATTR_RO(size); | ||||
| 
 | ||||
| static struct attribute *dma_buf_stats_default_attrs[] = { | ||||
| 	&exporter_name_attribute.attr, | ||||
| 	&size_attribute.attr, | ||||
| 	NULL, | ||||
| }; | ||||
| ATTRIBUTE_GROUPS(dma_buf_stats_default); | ||||
| 
 | ||||
| static void dma_buf_sysfs_release(struct kobject *kobj) | ||||
| { | ||||
| 	struct dma_buf_sysfs_entry *sysfs_entry; | ||||
| 
 | ||||
| 	sysfs_entry = to_dma_buf_entry_from_kobj(kobj); | ||||
| 	kfree(sysfs_entry); | ||||
| } | ||||
| 
 | ||||
| static struct kobj_type dma_buf_ktype = { | ||||
| 	.sysfs_ops = &dma_buf_stats_sysfs_ops, | ||||
| 	.release = dma_buf_sysfs_release, | ||||
| 	.default_groups = dma_buf_stats_default_groups, | ||||
| }; | ||||
| 
 | ||||
| #define to_dma_buf_attach_entry_from_kobj(x) container_of(x, struct dma_buf_attach_sysfs_entry, kobj) | ||||
| 
 | ||||
| struct dma_buf_attach_stats_attribute { | ||||
| 	struct attribute attr; | ||||
| 	ssize_t (*show)(struct dma_buf_attach_sysfs_entry *sysfs_entry, | ||||
| 			struct dma_buf_attach_stats_attribute *attr, char *buf); | ||||
| }; | ||||
| #define to_dma_buf_attach_stats_attr(x) container_of(x, struct dma_buf_attach_stats_attribute, attr) | ||||
| 
 | ||||
| static ssize_t dma_buf_attach_stats_attribute_show(struct kobject *kobj, | ||||
| 						   struct attribute *attr, | ||||
| 						   char *buf) | ||||
| { | ||||
| 	struct dma_buf_attach_stats_attribute *attribute; | ||||
| 	struct dma_buf_attach_sysfs_entry *sysfs_entry; | ||||
| 
 | ||||
| 	attribute = to_dma_buf_attach_stats_attr(attr); | ||||
| 	sysfs_entry = to_dma_buf_attach_entry_from_kobj(kobj); | ||||
| 
 | ||||
| 	if (!attribute->show) | ||||
| 		return -EIO; | ||||
| 
 | ||||
| 	return attribute->show(sysfs_entry, attribute, buf); | ||||
| } | ||||
| 
 | ||||
| static const struct sysfs_ops dma_buf_attach_stats_sysfs_ops = { | ||||
| 	.show = dma_buf_attach_stats_attribute_show, | ||||
| }; | ||||
| 
 | ||||
| static ssize_t map_counter_show(struct dma_buf_attach_sysfs_entry *sysfs_entry, | ||||
| 				struct dma_buf_attach_stats_attribute *attr, | ||||
| 				char *buf) | ||||
| { | ||||
| 	return sysfs_emit(buf, "%u\n", sysfs_entry->map_counter); | ||||
| } | ||||
| 
 | ||||
| static struct dma_buf_attach_stats_attribute map_counter_attribute = | ||||
| 	__ATTR_RO(map_counter); | ||||
| 
 | ||||
| static struct attribute *dma_buf_attach_stats_default_attrs[] = { | ||||
| 	&map_counter_attribute.attr, | ||||
| 	NULL, | ||||
| }; | ||||
| ATTRIBUTE_GROUPS(dma_buf_attach_stats_default); | ||||
| 
 | ||||
| static void dma_buf_attach_sysfs_release(struct kobject *kobj) | ||||
| { | ||||
| 	struct dma_buf_attach_sysfs_entry *sysfs_entry; | ||||
| 
 | ||||
| 	sysfs_entry = to_dma_buf_attach_entry_from_kobj(kobj); | ||||
| 	kfree(sysfs_entry); | ||||
| } | ||||
| 
 | ||||
| static struct kobj_type dma_buf_attach_ktype = { | ||||
| 	.sysfs_ops = &dma_buf_attach_stats_sysfs_ops, | ||||
| 	.release = dma_buf_attach_sysfs_release, | ||||
| 	.default_groups = dma_buf_attach_stats_default_groups, | ||||
| }; | ||||
| 
 | ||||
| void dma_buf_attach_stats_teardown(struct dma_buf_attachment *attach) | ||||
| { | ||||
| 	struct dma_buf_attach_sysfs_entry *sysfs_entry; | ||||
| 
 | ||||
| 	sysfs_entry = attach->sysfs_entry; | ||||
| 	if (!sysfs_entry) | ||||
| 		return; | ||||
| 
 | ||||
| 	sysfs_delete_link(&sysfs_entry->kobj, &attach->dev->kobj, "device"); | ||||
| 
 | ||||
| 	kobject_del(&sysfs_entry->kobj); | ||||
| 	kobject_put(&sysfs_entry->kobj); | ||||
| } | ||||
| 
 | ||||
| int dma_buf_attach_stats_setup(struct dma_buf_attachment *attach, | ||||
| 			       unsigned int uid) | ||||
| { | ||||
| 	struct dma_buf_attach_sysfs_entry *sysfs_entry; | ||||
| 	int ret; | ||||
| 	struct dma_buf *dmabuf; | ||||
| 
 | ||||
| 	if (!attach) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	dmabuf = attach->dmabuf; | ||||
| 
 | ||||
| 	sysfs_entry = kzalloc(sizeof(struct dma_buf_attach_sysfs_entry), | ||||
| 			      GFP_KERNEL); | ||||
| 	if (!sysfs_entry) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	sysfs_entry->kobj.kset = dmabuf->sysfs_entry->attach_stats_kset; | ||||
| 
 | ||||
| 	attach->sysfs_entry = sysfs_entry; | ||||
| 
 | ||||
| 	ret = kobject_init_and_add(&sysfs_entry->kobj, &dma_buf_attach_ktype, | ||||
| 				   NULL, "%u", uid); | ||||
| 	if (ret) | ||||
| 		goto kobj_err; | ||||
| 
 | ||||
| 	ret = sysfs_create_link(&sysfs_entry->kobj, &attach->dev->kobj, | ||||
| 				"device"); | ||||
| 	if (ret) | ||||
| 		goto link_err; | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| link_err: | ||||
| 	kobject_del(&sysfs_entry->kobj); | ||||
| kobj_err: | ||||
| 	kobject_put(&sysfs_entry->kobj); | ||||
| 	attach->sysfs_entry = NULL; | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| void dma_buf_stats_teardown(struct dma_buf *dmabuf) | ||||
| { | ||||
| 	struct dma_buf_sysfs_entry *sysfs_entry; | ||||
| 
 | ||||
| 	sysfs_entry = dmabuf->sysfs_entry; | ||||
| 	if (!sysfs_entry) | ||||
| 		return; | ||||
| 
 | ||||
| 	kset_unregister(sysfs_entry->attach_stats_kset); | ||||
| 	kobject_del(&sysfs_entry->kobj); | ||||
| 	kobject_put(&sysfs_entry->kobj); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Statistics files do not need to send uevents. */ | ||||
| static int dmabuf_sysfs_uevent_filter(struct kset *kset, struct kobject *kobj) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct kset_uevent_ops dmabuf_sysfs_no_uevent_ops = { | ||||
| 	.filter = dmabuf_sysfs_uevent_filter, | ||||
| }; | ||||
| 
 | ||||
| static struct kset *dma_buf_stats_kset; | ||||
| static struct kset *dma_buf_per_buffer_stats_kset; | ||||
| int dma_buf_init_sysfs_statistics(void) | ||||
| { | ||||
| 	dma_buf_stats_kset = kset_create_and_add("dmabuf", | ||||
| 						 &dmabuf_sysfs_no_uevent_ops, | ||||
| 						 kernel_kobj); | ||||
| 	if (!dma_buf_stats_kset) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	dma_buf_per_buffer_stats_kset = kset_create_and_add("buffers", | ||||
| 							    &dmabuf_sysfs_no_uevent_ops, | ||||
| 							    &dma_buf_stats_kset->kobj); | ||||
| 	if (!dma_buf_per_buffer_stats_kset) { | ||||
| 		kset_unregister(dma_buf_stats_kset); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| void dma_buf_uninit_sysfs_statistics(void) | ||||
| { | ||||
| 	kset_unregister(dma_buf_per_buffer_stats_kset); | ||||
| 	kset_unregister(dma_buf_stats_kset); | ||||
| } | ||||
| 
 | ||||
| int dma_buf_stats_setup(struct dma_buf *dmabuf) | ||||
| { | ||||
| 	struct dma_buf_sysfs_entry *sysfs_entry; | ||||
| 	int ret; | ||||
| 	struct kset *attach_stats_kset; | ||||
| 
 | ||||
| 	if (!dmabuf || !dmabuf->file) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (!dmabuf->exp_name) { | ||||
| 		pr_err("exporter name must not be empty if stats needed\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	sysfs_entry = kzalloc(sizeof(struct dma_buf_sysfs_entry), GFP_KERNEL); | ||||
| 	if (!sysfs_entry) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	sysfs_entry->kobj.kset = dma_buf_per_buffer_stats_kset; | ||||
| 	sysfs_entry->dmabuf = dmabuf; | ||||
| 
 | ||||
| 	dmabuf->sysfs_entry = sysfs_entry; | ||||
| 
 | ||||
| 	/* create the directory for buffer stats */ | ||||
| 	ret = kobject_init_and_add(&sysfs_entry->kobj, &dma_buf_ktype, NULL, | ||||
| 				   "%lu", file_inode(dmabuf->file)->i_ino); | ||||
| 	if (ret) | ||||
| 		goto err_sysfs_dmabuf; | ||||
| 
 | ||||
| 	/* create the directory for attachment stats */ | ||||
| 	attach_stats_kset = kset_create_and_add("attachments", | ||||
| 						&dmabuf_sysfs_no_uevent_ops, | ||||
| 						&sysfs_entry->kobj); | ||||
| 	if (!attach_stats_kset) { | ||||
| 		ret = -ENOMEM; | ||||
| 		goto err_sysfs_attach; | ||||
| 	} | ||||
| 
 | ||||
| 	sysfs_entry->attach_stats_kset = attach_stats_kset; | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err_sysfs_attach: | ||||
| 	kobject_del(&sysfs_entry->kobj); | ||||
| err_sysfs_dmabuf: | ||||
| 	kobject_put(&sysfs_entry->kobj); | ||||
| 	dmabuf->sysfs_entry = NULL; | ||||
| 	return ret; | ||||
| } | ||||
							
								
								
									
										62
									
								
								drivers/dma-buf/dma-buf-sysfs-stats.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								drivers/dma-buf/dma-buf-sysfs-stats.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0-only */ | ||||
| /*
 | ||||
|  * DMA-BUF sysfs statistics. | ||||
|  * | ||||
|  * Copyright (C) 2021 Google LLC. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _DMA_BUF_SYSFS_STATS_H | ||||
| #define _DMA_BUF_SYSFS_STATS_H | ||||
| 
 | ||||
| #ifdef CONFIG_DMABUF_SYSFS_STATS | ||||
| 
 | ||||
| int dma_buf_init_sysfs_statistics(void); | ||||
| void dma_buf_uninit_sysfs_statistics(void); | ||||
| 
 | ||||
| int dma_buf_stats_setup(struct dma_buf *dmabuf); | ||||
| int dma_buf_attach_stats_setup(struct dma_buf_attachment *attach, | ||||
| 			       unsigned int uid); | ||||
| static inline void dma_buf_update_attachment_map_count(struct dma_buf_attachment *attach, | ||||
| 						       int delta) | ||||
| { | ||||
| 	struct dma_buf_attach_sysfs_entry *entry = attach->sysfs_entry; | ||||
| 
 | ||||
| 	entry->map_counter += delta; | ||||
| } | ||||
| void dma_buf_stats_teardown(struct dma_buf *dmabuf); | ||||
| void dma_buf_attach_stats_teardown(struct dma_buf_attachment *attach); | ||||
| static inline unsigned int dma_buf_update_attach_uid(struct dma_buf *dmabuf) | ||||
| { | ||||
| 	struct dma_buf_sysfs_entry *entry = dmabuf->sysfs_entry; | ||||
| 
 | ||||
| 	return entry->attachment_uid++; | ||||
| } | ||||
| #else | ||||
| 
 | ||||
| static inline int dma_buf_init_sysfs_statistics(void) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline void dma_buf_uninit_sysfs_statistics(void) {} | ||||
| 
 | ||||
| static inline int dma_buf_stats_setup(struct dma_buf *dmabuf) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| static inline int dma_buf_attach_stats_setup(struct dma_buf_attachment *attach, | ||||
| 					     unsigned int uid) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline void dma_buf_stats_teardown(struct dma_buf *dmabuf) {} | ||||
| static inline void dma_buf_attach_stats_teardown(struct dma_buf_attachment *attach) {} | ||||
| static inline void dma_buf_update_attachment_map_count(struct dma_buf_attachment *attach, | ||||
| 						       int delta) {} | ||||
| static inline unsigned int dma_buf_update_attach_uid(struct dma_buf *dmabuf) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| #endif | ||||
| #endif // _DMA_BUF_SYSFS_STATS_H
 | ||||
| @ -29,6 +29,8 @@ | ||||
| #include <uapi/linux/dma-buf.h> | ||||
| #include <uapi/linux/magic.h> | ||||
| 
 | ||||
| #include "dma-buf-sysfs-stats.h" | ||||
| 
 | ||||
| static inline int is_dma_buf_file(struct file *); | ||||
| 
 | ||||
| struct dma_buf_list { | ||||
| @ -79,6 +81,7 @@ static void dma_buf_release(struct dentry *dentry) | ||||
| 	if (dmabuf->resv == (struct dma_resv *)&dmabuf[1]) | ||||
| 		dma_resv_fini(dmabuf->resv); | ||||
| 
 | ||||
| 	dma_buf_stats_teardown(dmabuf); | ||||
| 	module_put(dmabuf->owner); | ||||
| 	kfree(dmabuf->name); | ||||
| 	kfree(dmabuf); | ||||
| @ -580,6 +583,10 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info) | ||||
| 	file->f_mode |= FMODE_LSEEK; | ||||
| 	dmabuf->file = file; | ||||
| 
 | ||||
| 	ret = dma_buf_stats_setup(dmabuf); | ||||
| 	if (ret) | ||||
| 		goto err_sysfs; | ||||
| 
 | ||||
| 	mutex_init(&dmabuf->lock); | ||||
| 	INIT_LIST_HEAD(&dmabuf->attachments); | ||||
| 
 | ||||
| @ -589,6 +596,14 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info) | ||||
| 
 | ||||
| 	return dmabuf; | ||||
| 
 | ||||
| err_sysfs: | ||||
| 	/*
 | ||||
| 	 * Set file->f_path.dentry->d_fsdata to NULL so that when | ||||
| 	 * dma_buf_release() gets invoked by dentry_ops, it exits | ||||
| 	 * early before calling the release() dma_buf op. | ||||
| 	 */ | ||||
| 	file->f_path.dentry->d_fsdata = NULL; | ||||
| 	fput(file); | ||||
| err_dmabuf: | ||||
| 	kfree(dmabuf); | ||||
| err_module: | ||||
| @ -723,6 +738,7 @@ dma_buf_dynamic_attach(struct dma_buf *dmabuf, struct device *dev, | ||||
| { | ||||
| 	struct dma_buf_attachment *attach; | ||||
| 	int ret; | ||||
| 	unsigned int attach_uid; | ||||
| 
 | ||||
| 	if (WARN_ON(!dmabuf || !dev)) | ||||
| 		return ERR_PTR(-EINVAL); | ||||
| @ -748,8 +764,13 @@ dma_buf_dynamic_attach(struct dma_buf *dmabuf, struct device *dev, | ||||
| 	} | ||||
| 	dma_resv_lock(dmabuf->resv, NULL); | ||||
| 	list_add(&attach->node, &dmabuf->attachments); | ||||
| 	attach_uid = dma_buf_update_attach_uid(dmabuf); | ||||
| 	dma_resv_unlock(dmabuf->resv); | ||||
| 
 | ||||
| 	ret = dma_buf_attach_stats_setup(attach, attach_uid); | ||||
| 	if (ret) | ||||
| 		goto err_sysfs; | ||||
| 
 | ||||
| 	/* When either the importer or the exporter can't handle dynamic
 | ||||
| 	 * mappings we cache the mapping here to avoid issues with the | ||||
| 	 * reservation object lock. | ||||
| @ -776,6 +797,7 @@ dma_buf_dynamic_attach(struct dma_buf *dmabuf, struct device *dev, | ||||
| 			dma_resv_unlock(attach->dmabuf->resv); | ||||
| 		attach->sgt = sgt; | ||||
| 		attach->dir = DMA_BIDIRECTIONAL; | ||||
| 		dma_buf_update_attachment_map_count(attach, 1 /* delta */); | ||||
| 	} | ||||
| 
 | ||||
| 	return attach; | ||||
| @ -792,6 +814,7 @@ err_unlock: | ||||
| 	if (dma_buf_is_dynamic(attach->dmabuf)) | ||||
| 		dma_resv_unlock(attach->dmabuf->resv); | ||||
| 
 | ||||
| err_sysfs: | ||||
| 	dma_buf_detach(dmabuf, attach); | ||||
| 	return ERR_PTR(ret); | ||||
| } | ||||
| @ -841,6 +864,7 @@ void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach) | ||||
| 			dma_resv_lock(attach->dmabuf->resv, NULL); | ||||
| 
 | ||||
| 		__unmap_dma_buf(attach, attach->sgt, attach->dir); | ||||
| 		dma_buf_update_attachment_map_count(attach, -1 /* delta */); | ||||
| 
 | ||||
| 		if (dma_buf_is_dynamic(attach->dmabuf)) { | ||||
| 			dmabuf->ops->unpin(attach); | ||||
| @ -854,6 +878,7 @@ void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach) | ||||
| 	if (dmabuf->ops->detach) | ||||
| 		dmabuf->ops->detach(dmabuf, attach); | ||||
| 
 | ||||
| 	dma_buf_attach_stats_teardown(attach); | ||||
| 	kfree(attach); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(dma_buf_detach); | ||||
| @ -926,6 +951,9 @@ EXPORT_SYMBOL_GPL(dma_buf_unpin); | ||||
|  * the underlying backing storage is pinned for as long as a mapping exists, | ||||
|  * therefore users/importers should not hold onto a mapping for undue amounts of | ||||
|  * time. | ||||
|  * | ||||
|  * Important: Dynamic importers must wait for the exclusive fence of the struct | ||||
|  * dma_resv attached to the DMA-BUF first. | ||||
|  */ | ||||
| struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach, | ||||
| 					enum dma_data_direction direction) | ||||
| @ -993,6 +1021,9 @@ struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach, | ||||
| 	} | ||||
| #endif /* CONFIG_DMA_API_DEBUG */ | ||||
| 
 | ||||
| 	if (!IS_ERR(sg_table)) | ||||
| 		dma_buf_update_attachment_map_count(attach, 1 /* delta */); | ||||
| 
 | ||||
| 	return sg_table; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(dma_buf_map_attachment); | ||||
| @ -1030,6 +1061,8 @@ void dma_buf_unmap_attachment(struct dma_buf_attachment *attach, | ||||
| 	if (dma_buf_is_dynamic(attach->dmabuf) && | ||||
| 	    !IS_ENABLED(CONFIG_DMABUF_MOVE_NOTIFY)) | ||||
| 		dma_buf_unpin(attach); | ||||
| 
 | ||||
| 	dma_buf_update_attachment_map_count(attach, -1 /* delta */); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(dma_buf_unmap_attachment); | ||||
| 
 | ||||
| @ -1469,6 +1502,12 @@ static inline void dma_buf_uninit_debugfs(void) | ||||
| 
 | ||||
| static int __init dma_buf_init(void) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = dma_buf_init_sysfs_statistics(); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	dma_buf_mnt = kern_mount(&dma_buf_fs_type); | ||||
| 	if (IS_ERR(dma_buf_mnt)) | ||||
| 		return PTR_ERR(dma_buf_mnt); | ||||
| @ -1484,5 +1523,6 @@ static void __exit dma_buf_deinit(void) | ||||
| { | ||||
| 	dma_buf_uninit_debugfs(); | ||||
| 	kern_unmount(dma_buf_mnt); | ||||
| 	dma_buf_uninit_sysfs_statistics(); | ||||
| } | ||||
| __exitcall(dma_buf_deinit); | ||||
|  | ||||
| @ -137,6 +137,7 @@ static void dma_fence_chain_cb(struct dma_fence *f, struct dma_fence_cb *cb) | ||||
| 	struct dma_fence_chain *chain; | ||||
| 
 | ||||
| 	chain = container_of(cb, typeof(*chain), cb); | ||||
| 	init_irq_work(&chain->work, dma_fence_chain_irq_work); | ||||
| 	irq_work_queue(&chain->work); | ||||
| 	dma_fence_put(f); | ||||
| } | ||||
| @ -239,7 +240,6 @@ void dma_fence_chain_init(struct dma_fence_chain *chain, | ||||
| 	rcu_assign_pointer(chain->prev, prev); | ||||
| 	chain->fence = fence; | ||||
| 	chain->prev_seqno = 0; | ||||
| 	init_irq_work(&chain->work, dma_fence_chain_irq_work); | ||||
| 
 | ||||
| 	/* Try to reuse the context of the previous chain node. */ | ||||
| 	if (prev_chain && __dma_fence_is_later(seqno, prev->seqno, prev->ops)) { | ||||
|  | ||||
| @ -615,25 +615,21 @@ static inline int dma_resv_test_signaled_single(struct dma_fence *passed_fence) | ||||
|  */ | ||||
| bool dma_resv_test_signaled(struct dma_resv *obj, bool test_all) | ||||
| { | ||||
| 	unsigned int seq, shared_count; | ||||
| 	struct dma_fence *fence; | ||||
| 	unsigned int seq; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	rcu_read_lock(); | ||||
| retry: | ||||
| 	ret = true; | ||||
| 	shared_count = 0; | ||||
| 	seq = read_seqcount_begin(&obj->seq); | ||||
| 
 | ||||
| 	if (test_all) { | ||||
| 		struct dma_resv_list *fobj = dma_resv_shared_list(obj); | ||||
| 		unsigned int i; | ||||
| 
 | ||||
| 		if (fobj) | ||||
| 			shared_count = fobj->shared_count; | ||||
| 		unsigned int i, shared_count; | ||||
| 
 | ||||
| 		shared_count = fobj ? fobj->shared_count : 0; | ||||
| 		for (i = 0; i < shared_count; ++i) { | ||||
| 			struct dma_fence *fence; | ||||
| 
 | ||||
| 			fence = rcu_dereference(fobj->shared[i]); | ||||
| 			ret = dma_resv_test_signaled_single(fence); | ||||
| 			if (ret < 0) | ||||
| @ -641,23 +637,18 @@ retry: | ||||
| 			else if (!ret) | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 		if (read_seqcount_retry(&obj->seq, seq)) | ||||
| 	fence = dma_resv_excl_fence(obj); | ||||
| 	if (ret && fence) { | ||||
| 		ret = dma_resv_test_signaled_single(fence); | ||||
| 		if (ret < 0) | ||||
| 			goto retry; | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	if (!shared_count) { | ||||
| 		struct dma_fence *fence_excl = dma_resv_excl_fence(obj); | ||||
| 
 | ||||
| 		if (fence_excl) { | ||||
| 			ret = dma_resv_test_signaled_single(fence_excl); | ||||
| 			if (ret < 0) | ||||
| 				goto retry; | ||||
| 
 | ||||
| 			if (read_seqcount_retry(&obj->seq, seq)) | ||||
| 				goto retry; | ||||
| 		} | ||||
| 	} | ||||
| 	if (read_seqcount_retry(&obj->seq, seq)) | ||||
| 		goto retry; | ||||
| 
 | ||||
| 	rcu_read_unlock(); | ||||
| 	return ret; | ||||
|  | ||||
| @ -58,28 +58,20 @@ static struct dma_fence *mock_fence(void) | ||||
| 	return &f->base; | ||||
| } | ||||
| 
 | ||||
| static inline struct mock_chain { | ||||
| 	struct dma_fence_chain base; | ||||
| } *to_mock_chain(struct dma_fence *f) { | ||||
| 	return container_of(f, struct mock_chain, base.base); | ||||
| } | ||||
| 
 | ||||
| static struct dma_fence *mock_chain(struct dma_fence *prev, | ||||
| 				    struct dma_fence *fence, | ||||
| 				    u64 seqno) | ||||
| { | ||||
| 	struct mock_chain *f; | ||||
| 	struct dma_fence_chain *f; | ||||
| 
 | ||||
| 	f = kmalloc(sizeof(*f), GFP_KERNEL); | ||||
| 	f = dma_fence_chain_alloc(); | ||||
| 	if (!f) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	dma_fence_chain_init(&f->base, | ||||
| 			     dma_fence_get(prev), | ||||
| 			     dma_fence_get(fence), | ||||
| 	dma_fence_chain_init(f, dma_fence_get(prev), dma_fence_get(fence), | ||||
| 			     seqno); | ||||
| 
 | ||||
| 	return &f->base.base; | ||||
| 	return &f->base; | ||||
| } | ||||
| 
 | ||||
| static int sanitycheck(void *arg) | ||||
|  | ||||
| @ -11,9 +11,15 @@ | ||||
| #include <linux/shmem_fs.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/udmabuf.h> | ||||
| #include <linux/hugetlb.h> | ||||
| 
 | ||||
| static const u32    list_limit = 1024;  /* udmabuf_create_list->count limit */ | ||||
| static const size_t size_limit_mb = 64; /* total dmabuf size, in megabytes  */ | ||||
| static int list_limit = 1024; | ||||
| module_param(list_limit, int, 0644); | ||||
| MODULE_PARM_DESC(list_limit, "udmabuf_create_list->count limit. Default is 1024."); | ||||
| 
 | ||||
| static int size_limit_mb = 64; | ||||
| module_param(size_limit_mb, int, 0644); | ||||
| MODULE_PARM_DESC(size_limit_mb, "Max size of a dmabuf, in megabytes. Default is 64."); | ||||
| 
 | ||||
| struct udmabuf { | ||||
| 	pgoff_t pagecount; | ||||
| @ -160,10 +166,13 @@ static long udmabuf_create(struct miscdevice *device, | ||||
| { | ||||
| 	DEFINE_DMA_BUF_EXPORT_INFO(exp_info); | ||||
| 	struct file *memfd = NULL; | ||||
| 	struct address_space *mapping = NULL; | ||||
| 	struct udmabuf *ubuf; | ||||
| 	struct dma_buf *buf; | ||||
| 	pgoff_t pgoff, pgcnt, pgidx, pgbuf = 0, pglimit; | ||||
| 	struct page *page; | ||||
| 	struct page *page, *hpage = NULL; | ||||
| 	pgoff_t subpgoff, maxsubpgs; | ||||
| 	struct hstate *hpstate; | ||||
| 	int seals, ret = -EINVAL; | ||||
| 	u32 i, flags; | ||||
| 
 | ||||
| @ -194,7 +203,8 @@ static long udmabuf_create(struct miscdevice *device, | ||||
| 		memfd = fget(list[i].memfd); | ||||
| 		if (!memfd) | ||||
| 			goto err; | ||||
| 		if (!shmem_mapping(file_inode(memfd)->i_mapping)) | ||||
| 		mapping = file_inode(memfd)->i_mapping; | ||||
| 		if (!shmem_mapping(mapping) && !is_file_hugepages(memfd)) | ||||
| 			goto err; | ||||
| 		seals = memfd_fcntl(memfd, F_GET_SEALS, 0); | ||||
| 		if (seals == -EINVAL) | ||||
| @ -205,17 +215,48 @@ static long udmabuf_create(struct miscdevice *device, | ||||
| 			goto err; | ||||
| 		pgoff = list[i].offset >> PAGE_SHIFT; | ||||
| 		pgcnt = list[i].size   >> PAGE_SHIFT; | ||||
| 		if (is_file_hugepages(memfd)) { | ||||
| 			hpstate = hstate_file(memfd); | ||||
| 			pgoff = list[i].offset >> huge_page_shift(hpstate); | ||||
| 			subpgoff = (list[i].offset & | ||||
| 				    ~huge_page_mask(hpstate)) >> PAGE_SHIFT; | ||||
| 			maxsubpgs = huge_page_size(hpstate) >> PAGE_SHIFT; | ||||
| 		} | ||||
| 		for (pgidx = 0; pgidx < pgcnt; pgidx++) { | ||||
| 			page = shmem_read_mapping_page( | ||||
| 				file_inode(memfd)->i_mapping, pgoff + pgidx); | ||||
| 			if (IS_ERR(page)) { | ||||
| 				ret = PTR_ERR(page); | ||||
| 				goto err; | ||||
| 			if (is_file_hugepages(memfd)) { | ||||
| 				if (!hpage) { | ||||
| 					hpage = find_get_page_flags(mapping, pgoff, | ||||
| 								    FGP_ACCESSED); | ||||
| 					if (IS_ERR(hpage)) { | ||||
| 						ret = PTR_ERR(hpage); | ||||
| 						goto err; | ||||
| 					} | ||||
| 				} | ||||
| 				page = hpage + subpgoff; | ||||
| 				get_page(page); | ||||
| 				subpgoff++; | ||||
| 				if (subpgoff == maxsubpgs) { | ||||
| 					put_page(hpage); | ||||
| 					hpage = NULL; | ||||
| 					subpgoff = 0; | ||||
| 					pgoff++; | ||||
| 				} | ||||
| 			} else { | ||||
| 				page = shmem_read_mapping_page(mapping, | ||||
| 							       pgoff + pgidx); | ||||
| 				if (IS_ERR(page)) { | ||||
| 					ret = PTR_ERR(page); | ||||
| 					goto err; | ||||
| 				} | ||||
| 			} | ||||
| 			ubuf->pages[pgbuf++] = page; | ||||
| 		} | ||||
| 		fput(memfd); | ||||
| 		memfd = NULL; | ||||
| 		if (hpage) { | ||||
| 			put_page(hpage); | ||||
| 			hpage = NULL; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	exp_info.ops  = &udmabuf_ops; | ||||
|  | ||||
| @ -35,6 +35,11 @@ config DRM_MIPI_DSI | ||||
| 	bool | ||||
| 	depends on DRM | ||||
| 
 | ||||
| config DRM_DP_AUX_BUS | ||||
| 	tristate | ||||
| 	depends on DRM | ||||
| 	depends on OF | ||||
| 
 | ||||
| config DRM_DP_AUX_CHARDEV | ||||
| 	bool "DRM DP AUX Interface" | ||||
| 	depends on DRM | ||||
| @ -317,8 +322,6 @@ source "drivers/gpu/drm/tilcdc/Kconfig" | ||||
| 
 | ||||
| source "drivers/gpu/drm/qxl/Kconfig" | ||||
| 
 | ||||
| source "drivers/gpu/drm/bochs/Kconfig" | ||||
| 
 | ||||
| source "drivers/gpu/drm/virtio/Kconfig" | ||||
| 
 | ||||
| source "drivers/gpu/drm/msm/Kconfig" | ||||
|  | ||||
| @ -33,6 +33,8 @@ drm-$(CONFIG_PCI) += drm_pci.o | ||||
| drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o | ||||
| drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o | ||||
| 
 | ||||
| obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o | ||||
| 
 | ||||
| drm_vram_helper-y := drm_gem_vram_helper.o | ||||
| obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o | ||||
| 
 | ||||
| @ -96,7 +98,6 @@ obj-y			+= omapdrm/ | ||||
| obj-$(CONFIG_DRM_SUN4I) += sun4i/ | ||||
| obj-y			+= tilcdc/ | ||||
| obj-$(CONFIG_DRM_QXL) += qxl/ | ||||
| obj-$(CONFIG_DRM_BOCHS) += bochs/ | ||||
| obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio/ | ||||
| obj-$(CONFIG_DRM_MSM) += msm/ | ||||
| obj-$(CONFIG_DRM_TEGRA) += tegra/ | ||||
|  | ||||
| @ -34,6 +34,7 @@ struct amdgpu_fpriv; | ||||
| struct amdgpu_bo_list_entry { | ||||
| 	struct ttm_validate_buffer	tv; | ||||
| 	struct amdgpu_bo_va		*bo_va; | ||||
| 	struct dma_fence_chain		*chain; | ||||
| 	uint32_t			priority; | ||||
| 	struct page			**user_pages; | ||||
| 	bool				user_invalidated; | ||||
|  | ||||
| @ -572,6 +572,20 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	amdgpu_bo_list_for_each_entry(e, p->bo_list) { | ||||
| 		struct amdgpu_bo *bo = ttm_to_amdgpu_bo(e->tv.bo); | ||||
| 
 | ||||
| 		e->bo_va = amdgpu_vm_bo_find(vm, bo); | ||||
| 
 | ||||
| 		if (bo->tbo.base.dma_buf && !amdgpu_bo_explicit_sync(bo)) { | ||||
| 			e->chain = dma_fence_chain_alloc(); | ||||
| 			if (!e->chain) { | ||||
| 				r = -ENOMEM; | ||||
| 				goto error_validate; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	amdgpu_cs_get_threshold_for_moves(p->adev, &p->bytes_moved_threshold, | ||||
| 					  &p->bytes_moved_vis_threshold); | ||||
| 	p->bytes_moved = 0; | ||||
| @ -599,15 +613,6 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, | ||||
| 	gws = p->bo_list->gws_obj; | ||||
| 	oa = p->bo_list->oa_obj; | ||||
| 
 | ||||
| 	amdgpu_bo_list_for_each_entry(e, p->bo_list) { | ||||
| 		struct amdgpu_bo *bo = ttm_to_amdgpu_bo(e->tv.bo); | ||||
| 
 | ||||
| 		/* Make sure we use the exclusive slot for shared BOs */ | ||||
| 		if (bo->prime_shared_count) | ||||
| 			e->tv.num_shared = 0; | ||||
| 		e->bo_va = amdgpu_vm_bo_find(vm, bo); | ||||
| 	} | ||||
| 
 | ||||
| 	if (gds) { | ||||
| 		p->job->gds_base = amdgpu_bo_gpu_offset(gds) >> PAGE_SHIFT; | ||||
| 		p->job->gds_size = amdgpu_bo_size(gds) >> PAGE_SHIFT; | ||||
| @ -629,8 +634,13 @@ static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p, | ||||
| 	} | ||||
| 
 | ||||
| error_validate: | ||||
| 	if (r) | ||||
| 	if (r) { | ||||
| 		amdgpu_bo_list_for_each_entry(e, p->bo_list) { | ||||
| 			dma_fence_chain_free(e->chain); | ||||
| 			e->chain = NULL; | ||||
| 		} | ||||
| 		ttm_eu_backoff_reservation(&p->ticket, &p->validated); | ||||
| 	} | ||||
| out: | ||||
| 	return r; | ||||
| } | ||||
| @ -670,9 +680,17 @@ static void amdgpu_cs_parser_fini(struct amdgpu_cs_parser *parser, int error, | ||||
| { | ||||
| 	unsigned i; | ||||
| 
 | ||||
| 	if (error && backoff) | ||||
| 	if (error && backoff) { | ||||
| 		struct amdgpu_bo_list_entry *e; | ||||
| 
 | ||||
| 		amdgpu_bo_list_for_each_entry(e, parser->bo_list) { | ||||
| 			dma_fence_chain_free(e->chain); | ||||
| 			e->chain = NULL; | ||||
| 		} | ||||
| 
 | ||||
| 		ttm_eu_backoff_reservation(&parser->ticket, | ||||
| 					   &parser->validated); | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < parser->num_post_deps; i++) { | ||||
| 		drm_syncobj_put(parser->post_deps[i].syncobj); | ||||
| @ -1109,7 +1127,7 @@ static int amdgpu_cs_process_syncobj_timeline_out_dep(struct amdgpu_cs_parser *p | ||||
| 
 | ||||
| 		dep->chain = NULL; | ||||
| 		if (syncobj_deps[i].point) { | ||||
| 			dep->chain = kmalloc(sizeof(*dep->chain), GFP_KERNEL); | ||||
| 			dep->chain = dma_fence_chain_alloc(); | ||||
| 			if (!dep->chain) | ||||
| 				return -ENOMEM; | ||||
| 		} | ||||
| @ -1117,7 +1135,7 @@ static int amdgpu_cs_process_syncobj_timeline_out_dep(struct amdgpu_cs_parser *p | ||||
| 		dep->syncobj = drm_syncobj_find(p->filp, | ||||
| 						syncobj_deps[i].handle); | ||||
| 		if (!dep->syncobj) { | ||||
| 			kfree(dep->chain); | ||||
| 			dma_fence_chain_free(dep->chain); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 		dep->point = syncobj_deps[i].point; | ||||
| @ -1245,6 +1263,28 @@ static int amdgpu_cs_submit(struct amdgpu_cs_parser *p, | ||||
| 
 | ||||
| 	amdgpu_vm_move_to_lru_tail(p->adev, &fpriv->vm); | ||||
| 
 | ||||
| 	amdgpu_bo_list_for_each_entry(e, p->bo_list) { | ||||
| 		struct dma_resv *resv = e->tv.bo->base.resv; | ||||
| 		struct dma_fence_chain *chain = e->chain; | ||||
| 
 | ||||
| 		if (!chain) | ||||
| 			continue; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Work around dma_resv shortcommings by wrapping up the | ||||
| 		 * submission in a dma_fence_chain and add it as exclusive | ||||
| 		 * fence, but first add the submission as shared fence to make | ||||
| 		 * sure that shared fences never signal before the exclusive | ||||
| 		 * one. | ||||
| 		 */ | ||||
| 		dma_fence_chain_init(chain, dma_resv_excl_fence(resv), | ||||
| 				     dma_fence_get(p->fence), 1); | ||||
| 
 | ||||
| 		dma_resv_add_shared_fence(resv, p->fence); | ||||
| 		rcu_assign_pointer(resv->fence_excl, &chain->base); | ||||
| 		e->chain = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	ttm_eu_fence_buffer_objects(&p->ticket, &p->validated, p->fence); | ||||
| 	mutex_unlock(&p->adev->notifier_lock); | ||||
| 
 | ||||
|  | ||||
| @ -42,48 +42,6 @@ | ||||
| #include <linux/pci-p2pdma.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| 
 | ||||
| static int | ||||
| __dma_resv_make_exclusive(struct dma_resv *obj) | ||||
| { | ||||
| 	struct dma_fence **fences; | ||||
| 	unsigned int count; | ||||
| 	int r; | ||||
| 
 | ||||
| 	if (!dma_resv_shared_list(obj)) /* no shared fences to convert */ | ||||
| 		return 0; | ||||
| 
 | ||||
| 	r = dma_resv_get_fences(obj, NULL, &count, &fences); | ||||
| 	if (r) | ||||
| 		return r; | ||||
| 
 | ||||
| 	if (count == 0) { | ||||
| 		/* Now that was unexpected. */ | ||||
| 	} else if (count == 1) { | ||||
| 		dma_resv_add_excl_fence(obj, fences[0]); | ||||
| 		dma_fence_put(fences[0]); | ||||
| 		kfree(fences); | ||||
| 	} else { | ||||
| 		struct dma_fence_array *array; | ||||
| 
 | ||||
| 		array = dma_fence_array_create(count, fences, | ||||
| 					       dma_fence_context_alloc(1), 0, | ||||
| 					       false); | ||||
| 		if (!array) | ||||
| 			goto err_fences_put; | ||||
| 
 | ||||
| 		dma_resv_add_excl_fence(obj, &array->base); | ||||
| 		dma_fence_put(&array->base); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err_fences_put: | ||||
| 	while (count--) | ||||
| 		dma_fence_put(fences[count]); | ||||
| 	kfree(fences); | ||||
| 	return -ENOMEM; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * amdgpu_dma_buf_attach - &dma_buf_ops.attach implementation | ||||
|  * | ||||
| @ -110,24 +68,6 @@ static int amdgpu_dma_buf_attach(struct dma_buf *dmabuf, | ||||
| 	if (r < 0) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	r = amdgpu_bo_reserve(bo, false); | ||||
| 	if (unlikely(r != 0)) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We only create shared fences for internal use, but importers | ||||
| 	 * of the dmabuf rely on exclusive fences for implicitly | ||||
| 	 * tracking write hazards. As any of the current fences may | ||||
| 	 * correspond to a write, we need to convert all existing | ||||
| 	 * fences on the reservation object into a single exclusive | ||||
| 	 * fence. | ||||
| 	 */ | ||||
| 	r = __dma_resv_make_exclusive(bo->tbo.base.resv); | ||||
| 	if (r) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	bo->prime_shared_count++; | ||||
| 	amdgpu_bo_unreserve(bo); | ||||
| 	return 0; | ||||
| 
 | ||||
| out: | ||||
| @ -150,9 +90,6 @@ static void amdgpu_dma_buf_detach(struct dma_buf *dmabuf, | ||||
| 	struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); | ||||
| 	struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); | ||||
| 
 | ||||
| 	if (attach->dev->driver != adev->dev->driver && bo->prime_shared_count) | ||||
| 		bo->prime_shared_count--; | ||||
| 
 | ||||
| 	pm_runtime_mark_last_busy(adev_to_drm(adev)->dev); | ||||
| 	pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); | ||||
| } | ||||
| @ -418,8 +355,6 @@ amdgpu_dma_buf_create_obj(struct drm_device *dev, struct dma_buf *dma_buf) | ||||
| 	bo = gem_to_amdgpu_bo(gobj); | ||||
| 	bo->allowed_domains = AMDGPU_GEM_DOMAIN_GTT; | ||||
| 	bo->preferred_domains = AMDGPU_GEM_DOMAIN_GTT; | ||||
| 	if (dma_buf->ops != &amdgpu_dmabuf_ops) | ||||
| 		bo->prime_shared_count = 1; | ||||
| 
 | ||||
| 	dma_resv_unlock(resv); | ||||
| 	return gobj; | ||||
|  | ||||
| @ -1281,7 +1281,7 @@ static int amdgpu_pci_probe(struct pci_dev *pdev, | ||||
| #endif | ||||
| 
 | ||||
| 	/* Get rid of things like offb */ | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "amdgpudrmfb"); | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &amdgpu_kms_driver); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
|  | ||||
| @ -490,7 +490,7 @@ int amdgpu_fence_driver_init_ring(struct amdgpu_ring *ring, | ||||
| 
 | ||||
| 	r = drm_sched_init(&ring->sched, &amdgpu_sched_ops, | ||||
| 			   num_hw_submission, amdgpu_job_hang_limit, | ||||
| 			   timeout, sched_score, ring->name); | ||||
| 			   timeout, NULL, sched_score, ring->name); | ||||
| 	if (r) { | ||||
| 		DRM_ERROR("Failed to create scheduler on ring %s.\n", | ||||
| 			  ring->name); | ||||
|  | ||||
| @ -829,7 +829,8 @@ int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data, | ||||
| 		break; | ||||
| 	} | ||||
| 	case AMDGPU_GEM_OP_SET_PLACEMENT: | ||||
| 		if (robj->prime_shared_count && (args->value & AMDGPU_GEM_DOMAIN_VRAM)) { | ||||
| 		if (robj->tbo.base.import_attach && | ||||
| 		    args->value & AMDGPU_GEM_DOMAIN_VRAM) { | ||||
| 			r = -EINVAL; | ||||
| 			amdgpu_bo_unreserve(robj); | ||||
| 			break; | ||||
|  | ||||
| @ -132,14 +132,11 @@ static int amdgpu_gtt_mgr_new(struct ttm_resource_manager *man, | ||||
| 	struct amdgpu_gtt_node *node; | ||||
| 	int r; | ||||
| 
 | ||||
| 	spin_lock(&mgr->lock); | ||||
| 	if (tbo->resource && tbo->resource->mem_type != TTM_PL_TT && | ||||
| 	    atomic64_read(&mgr->available) < num_pages) { | ||||
| 		spin_unlock(&mgr->lock); | ||||
| 	if (!(place->flags & TTM_PL_FLAG_TEMPORARY) && | ||||
| 	    atomic64_add_return(num_pages, &mgr->used) >  man->size) { | ||||
| 		atomic64_sub(num_pages, &mgr->used); | ||||
| 		return -ENOSPC; | ||||
| 	} | ||||
| 	atomic64_sub(num_pages, &mgr->available); | ||||
| 	spin_unlock(&mgr->lock); | ||||
| 
 | ||||
| 	node = kzalloc(struct_size(node, base.mm_nodes, 1), GFP_KERNEL); | ||||
| 	if (!node) { | ||||
| @ -175,7 +172,8 @@ err_free: | ||||
| 	kfree(node); | ||||
| 
 | ||||
| err_out: | ||||
| 	atomic64_add(num_pages, &mgr->available); | ||||
| 	if (!(place->flags & TTM_PL_FLAG_TEMPORARY)) | ||||
| 		atomic64_sub(num_pages, &mgr->used); | ||||
| 
 | ||||
| 	return r; | ||||
| } | ||||
| @ -198,7 +196,9 @@ static void amdgpu_gtt_mgr_del(struct ttm_resource_manager *man, | ||||
| 	if (drm_mm_node_allocated(&node->base.mm_nodes[0])) | ||||
| 		drm_mm_remove_node(&node->base.mm_nodes[0]); | ||||
| 	spin_unlock(&mgr->lock); | ||||
| 	atomic64_add(res->num_pages, &mgr->available); | ||||
| 
 | ||||
| 	if (!(res->placement & TTM_PL_FLAG_TEMPORARY)) | ||||
| 		atomic64_sub(res->num_pages, &mgr->used); | ||||
| 
 | ||||
| 	kfree(node); | ||||
| } | ||||
| @ -213,9 +213,8 @@ static void amdgpu_gtt_mgr_del(struct ttm_resource_manager *man, | ||||
| uint64_t amdgpu_gtt_mgr_usage(struct ttm_resource_manager *man) | ||||
| { | ||||
| 	struct amdgpu_gtt_mgr *mgr = to_gtt_mgr(man); | ||||
| 	s64 result = man->size - atomic64_read(&mgr->available); | ||||
| 
 | ||||
| 	return (result > 0 ? result : 0) * PAGE_SIZE; | ||||
| 	return atomic64_read(&mgr->used) * PAGE_SIZE; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
| @ -265,9 +264,8 @@ static void amdgpu_gtt_mgr_debug(struct ttm_resource_manager *man, | ||||
| 	drm_mm_print(&mgr->mm, printer); | ||||
| 	spin_unlock(&mgr->lock); | ||||
| 
 | ||||
| 	drm_printf(printer, "man size:%llu pages, gtt available:%lld pages, usage:%lluMB\n", | ||||
| 		   man->size, (u64)atomic64_read(&mgr->available), | ||||
| 		   amdgpu_gtt_mgr_usage(man) >> 20); | ||||
| 	drm_printf(printer, "man size:%llu pages,  gtt used:%llu pages\n", | ||||
| 		   man->size, atomic64_read(&mgr->used)); | ||||
| } | ||||
| 
 | ||||
| static const struct ttm_resource_manager_func amdgpu_gtt_mgr_func = { | ||||
| @ -299,7 +297,7 @@ int amdgpu_gtt_mgr_init(struct amdgpu_device *adev, uint64_t gtt_size) | ||||
| 	size = (adev->gmc.gart_size >> PAGE_SHIFT) - start; | ||||
| 	drm_mm_init(&mgr->mm, start, size); | ||||
| 	spin_lock_init(&mgr->lock); | ||||
| 	atomic64_set(&mgr->available, gtt_size >> PAGE_SHIFT); | ||||
| 	atomic64_set(&mgr->used, 0); | ||||
| 
 | ||||
| 	ttm_set_driver_manager(&adev->mman.bdev, TTM_PL_TT, &mgr->manager); | ||||
| 	ttm_resource_manager_set_used(man, true); | ||||
|  | ||||
| @ -617,7 +617,7 @@ void amdgpu_irq_gpu_reset_resume_helper(struct amdgpu_device *adev) | ||||
| int amdgpu_irq_get(struct amdgpu_device *adev, struct amdgpu_irq_src *src, | ||||
| 		   unsigned type) | ||||
| { | ||||
| 	if (!adev_to_drm(adev)->irq_enabled) | ||||
| 	if (!adev->irq.installed) | ||||
| 		return -ENOENT; | ||||
| 
 | ||||
| 	if (type >= src->num_types) | ||||
| @ -647,7 +647,7 @@ int amdgpu_irq_get(struct amdgpu_device *adev, struct amdgpu_irq_src *src, | ||||
| int amdgpu_irq_put(struct amdgpu_device *adev, struct amdgpu_irq_src *src, | ||||
| 		   unsigned type) | ||||
| { | ||||
| 	if (!adev_to_drm(adev)->irq_enabled) | ||||
| 	if (!adev->irq.installed) | ||||
| 		return -ENOENT; | ||||
| 
 | ||||
| 	if (type >= src->num_types) | ||||
| @ -678,7 +678,7 @@ int amdgpu_irq_put(struct amdgpu_device *adev, struct amdgpu_irq_src *src, | ||||
| bool amdgpu_irq_enabled(struct amdgpu_device *adev, struct amdgpu_irq_src *src, | ||||
| 			unsigned type) | ||||
| { | ||||
| 	if (!adev_to_drm(adev)->irq_enabled) | ||||
| 	if (!adev->irq.installed) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (type >= src->num_types) | ||||
|  | ||||
| @ -196,7 +196,7 @@ void amdgpu_bo_placement_from_domain(struct amdgpu_bo *abo, u32 domain) | ||||
| 		c++; | ||||
| 	} | ||||
| 
 | ||||
| 	BUG_ON(c >= AMDGPU_BO_MAX_PLACEMENTS); | ||||
| 	BUG_ON(c > AMDGPU_BO_MAX_PLACEMENTS); | ||||
| 
 | ||||
| 	placement->num_placement = c; | ||||
| 	placement->placement = places; | ||||
| @ -913,7 +913,7 @@ int amdgpu_bo_pin_restricted(struct amdgpu_bo *bo, u32 domain, | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	/* A shared bo cannot be migrated to VRAM */ | ||||
| 	if (bo->prime_shared_count || bo->tbo.base.import_attach) { | ||||
| 	if (bo->tbo.base.import_attach) { | ||||
| 		if (domain & AMDGPU_GEM_DOMAIN_GTT) | ||||
| 			domain = AMDGPU_GEM_DOMAIN_GTT; | ||||
| 		else | ||||
|  | ||||
| @ -100,7 +100,6 @@ struct amdgpu_bo { | ||||
| 	struct ttm_buffer_object	tbo; | ||||
| 	struct ttm_bo_kmap_obj		kmap; | ||||
| 	u64				flags; | ||||
| 	unsigned			prime_shared_count; | ||||
| 	/* per VM structure for page tables and with virtual addresses */ | ||||
| 	struct amdgpu_vm_bo_base	*vm_bo; | ||||
| 	/* Constant after initialization */ | ||||
|  | ||||
| @ -28,6 +28,8 @@ | ||||
|  *    Christian König <christian.koenig@amd.com> | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/dma-fence-chain.h> | ||||
| 
 | ||||
| #include "amdgpu.h" | ||||
| #include "amdgpu_trace.h" | ||||
| #include "amdgpu_amdkfd.h" | ||||
| @ -186,6 +188,55 @@ int amdgpu_sync_vm_fence(struct amdgpu_sync *sync, struct dma_fence *fence) | ||||
| 	return amdgpu_sync_fence(sync, fence); | ||||
| } | ||||
| 
 | ||||
| /* Determine based on the owner and mode if we should sync to a fence or not */ | ||||
| static bool amdgpu_sync_test_fence(struct amdgpu_device *adev, | ||||
| 				   enum amdgpu_sync_mode mode, | ||||
| 				   void *owner, struct dma_fence *f) | ||||
| { | ||||
| 	void *fence_owner = amdgpu_sync_get_owner(f); | ||||
| 
 | ||||
| 	/* Always sync to moves, no matter what */ | ||||
| 	if (fence_owner == AMDGPU_FENCE_OWNER_UNDEFINED) | ||||
| 		return true; | ||||
| 
 | ||||
| 	/* We only want to trigger KFD eviction fences on
 | ||||
| 	 * evict or move jobs. Skip KFD fences otherwise. | ||||
| 	 */ | ||||
| 	if (fence_owner == AMDGPU_FENCE_OWNER_KFD && | ||||
| 	    owner != AMDGPU_FENCE_OWNER_UNDEFINED) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/* Never sync to VM updates either. */ | ||||
| 	if (fence_owner == AMDGPU_FENCE_OWNER_VM && | ||||
| 	    owner != AMDGPU_FENCE_OWNER_UNDEFINED) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/* Ignore fences depending on the sync mode */ | ||||
| 	switch (mode) { | ||||
| 	case AMDGPU_SYNC_ALWAYS: | ||||
| 		return true; | ||||
| 
 | ||||
| 	case AMDGPU_SYNC_NE_OWNER: | ||||
| 		if (amdgpu_sync_same_dev(adev, f) && | ||||
| 		    fence_owner == owner) | ||||
| 			return false; | ||||
| 		break; | ||||
| 
 | ||||
| 	case AMDGPU_SYNC_EQ_OWNER: | ||||
| 		if (amdgpu_sync_same_dev(adev, f) && | ||||
| 		    fence_owner != owner) | ||||
| 			return false; | ||||
| 		break; | ||||
| 
 | ||||
| 	case AMDGPU_SYNC_EXPLICIT: | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	WARN(debug_evictions && fence_owner == AMDGPU_FENCE_OWNER_KFD, | ||||
| 	     "Adding eviction fence to sync obj"); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * amdgpu_sync_resv - sync to a reservation object | ||||
|  * | ||||
| @ -211,67 +262,34 @@ int amdgpu_sync_resv(struct amdgpu_device *adev, struct amdgpu_sync *sync, | ||||
| 
 | ||||
| 	/* always sync to the exclusive fence */ | ||||
| 	f = dma_resv_excl_fence(resv); | ||||
| 	r = amdgpu_sync_fence(sync, f); | ||||
| 	dma_fence_chain_for_each(f, f) { | ||||
| 		struct dma_fence_chain *chain = to_dma_fence_chain(f); | ||||
| 
 | ||||
| 		if (amdgpu_sync_test_fence(adev, mode, owner, chain ? | ||||
| 					   chain->fence : f)) { | ||||
| 			r = amdgpu_sync_fence(sync, f); | ||||
| 			dma_fence_put(f); | ||||
| 			if (r) | ||||
| 				return r; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	flist = dma_resv_shared_list(resv); | ||||
| 	if (!flist || r) | ||||
| 		return r; | ||||
| 	if (!flist) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	for (i = 0; i < flist->shared_count; ++i) { | ||||
| 		void *fence_owner; | ||||
| 
 | ||||
| 		f = rcu_dereference_protected(flist->shared[i], | ||||
| 					      dma_resv_held(resv)); | ||||
| 
 | ||||
| 		fence_owner = amdgpu_sync_get_owner(f); | ||||
| 
 | ||||
| 		/* Always sync to moves, no matter what */ | ||||
| 		if (fence_owner == AMDGPU_FENCE_OWNER_UNDEFINED) { | ||||
| 		if (amdgpu_sync_test_fence(adev, mode, owner, f)) { | ||||
| 			r = amdgpu_sync_fence(sync, f); | ||||
| 			if (r) | ||||
| 				break; | ||||
| 				return r; | ||||
| 		} | ||||
| 
 | ||||
| 		/* We only want to trigger KFD eviction fences on
 | ||||
| 		 * evict or move jobs. Skip KFD fences otherwise. | ||||
| 		 */ | ||||
| 		if (fence_owner == AMDGPU_FENCE_OWNER_KFD && | ||||
| 		    owner != AMDGPU_FENCE_OWNER_UNDEFINED) | ||||
| 			continue; | ||||
| 
 | ||||
| 		/* Never sync to VM updates either. */ | ||||
| 		if (fence_owner == AMDGPU_FENCE_OWNER_VM && | ||||
| 		    owner != AMDGPU_FENCE_OWNER_UNDEFINED) | ||||
| 			continue; | ||||
| 
 | ||||
| 		/* Ignore fences depending on the sync mode */ | ||||
| 		switch (mode) { | ||||
| 		case AMDGPU_SYNC_ALWAYS: | ||||
| 			break; | ||||
| 
 | ||||
| 		case AMDGPU_SYNC_NE_OWNER: | ||||
| 			if (amdgpu_sync_same_dev(adev, f) && | ||||
| 			    fence_owner == owner) | ||||
| 				continue; | ||||
| 			break; | ||||
| 
 | ||||
| 		case AMDGPU_SYNC_EQ_OWNER: | ||||
| 			if (amdgpu_sync_same_dev(adev, f) && | ||||
| 			    fence_owner != owner) | ||||
| 				continue; | ||||
| 			break; | ||||
| 
 | ||||
| 		case AMDGPU_SYNC_EXPLICIT: | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		WARN(debug_evictions && fence_owner == AMDGPU_FENCE_OWNER_KFD, | ||||
| 		     "Adding eviction fence to sync obj"); | ||||
| 		r = amdgpu_sync_fence(sync, f); | ||||
| 		if (r) | ||||
| 			break; | ||||
| 	} | ||||
| 	return r; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | ||||
| @ -149,14 +149,16 @@ static void amdgpu_evict_flags(struct ttm_buffer_object *bo, | ||||
| 			 * BOs to be evicted from VRAM | ||||
| 			 */ | ||||
| 			amdgpu_bo_placement_from_domain(abo, AMDGPU_GEM_DOMAIN_VRAM | | ||||
| 							 AMDGPU_GEM_DOMAIN_GTT); | ||||
| 							AMDGPU_GEM_DOMAIN_GTT | | ||||
| 							AMDGPU_GEM_DOMAIN_CPU); | ||||
| 			abo->placements[0].fpfn = adev->gmc.visible_vram_size >> PAGE_SHIFT; | ||||
| 			abo->placements[0].lpfn = 0; | ||||
| 			abo->placement.busy_placement = &abo->placements[1]; | ||||
| 			abo->placement.num_busy_placement = 1; | ||||
| 		} else { | ||||
| 			/* Move to GTT memory */ | ||||
| 			amdgpu_bo_placement_from_domain(abo, AMDGPU_GEM_DOMAIN_GTT); | ||||
| 			amdgpu_bo_placement_from_domain(abo, AMDGPU_GEM_DOMAIN_GTT | | ||||
| 							AMDGPU_GEM_DOMAIN_CPU); | ||||
| 		} | ||||
| 		break; | ||||
| 	case TTM_PL_TT: | ||||
| @ -521,7 +523,7 @@ static int amdgpu_bo_move(struct ttm_buffer_object *bo, bool evict, | ||||
| 			hop->fpfn = 0; | ||||
| 			hop->lpfn = 0; | ||||
| 			hop->mem_type = TTM_PL_TT; | ||||
| 			hop->flags = 0; | ||||
| 			hop->flags = TTM_PL_FLAG_TEMPORARY; | ||||
| 			return -EMULTIHOP; | ||||
| 		} | ||||
| 
 | ||||
|  | ||||
| @ -52,7 +52,7 @@ struct amdgpu_gtt_mgr { | ||||
| 	struct ttm_resource_manager manager; | ||||
| 	struct drm_mm mm; | ||||
| 	spinlock_t lock; | ||||
| 	atomic64_t available; | ||||
| 	atomic64_t used; | ||||
| }; | ||||
| 
 | ||||
| struct amdgpu_preempt_mgr { | ||||
|  | ||||
| @ -13,7 +13,6 @@ | ||||
| #include <drm/drm_fb_helper.h> | ||||
| #include <drm/drm_gem_cma_helper.h> | ||||
| #include <drm/drm_gem_framebuffer_helper.h> | ||||
| #include <drm/drm_irq.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_probe_helper.h> | ||||
| #include <drm/drm_vblank.h> | ||||
| @ -301,8 +300,6 @@ struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev) | ||||
| 	if (err) | ||||
| 		goto free_component_binding; | ||||
| 
 | ||||
| 	drm->irq_enabled = true; | ||||
| 
 | ||||
| 	drm_kms_helper_poll_init(drm); | ||||
| 
 | ||||
| 	err = drm_dev_register(drm, 0); | ||||
| @ -313,7 +310,6 @@ struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev) | ||||
| 
 | ||||
| free_interrupts: | ||||
| 	drm_kms_helper_poll_fini(drm); | ||||
| 	drm->irq_enabled = false; | ||||
| free_component_binding: | ||||
| 	component_unbind_all(mdev->dev, drm); | ||||
| cleanup_mode_config: | ||||
| @ -331,7 +327,6 @@ void komeda_kms_detach(struct komeda_kms_dev *kms) | ||||
| 	drm_dev_unregister(drm); | ||||
| 	drm_kms_helper_poll_fini(drm); | ||||
| 	drm_atomic_helper_shutdown(drm); | ||||
| 	drm->irq_enabled = false; | ||||
| 	component_unbind_all(mdev->dev, drm); | ||||
| 	drm_mode_config_cleanup(drm); | ||||
| 	komeda_kms_cleanup_private_objs(kms); | ||||
|  | ||||
| @ -847,8 +847,6 @@ static int malidp_bind(struct device *dev) | ||||
| 	if (ret < 0) | ||||
| 		goto irq_init_fail; | ||||
| 
 | ||||
| 	drm->irq_enabled = true; | ||||
| 
 | ||||
| 	ret = drm_vblank_init(drm, drm->mode_config.num_crtc); | ||||
| 	if (ret < 0) { | ||||
| 		DRM_ERROR("failed to initialise vblank\n"); | ||||
| @ -874,7 +872,6 @@ register_fail: | ||||
| vblank_fail: | ||||
| 	malidp_se_irq_fini(hwdev); | ||||
| 	malidp_de_irq_fini(hwdev); | ||||
| 	drm->irq_enabled = false; | ||||
| irq_init_fail: | ||||
| 	drm_atomic_helper_shutdown(drm); | ||||
| 	component_unbind_all(dev, drm); | ||||
| @ -909,7 +906,6 @@ static void malidp_unbind(struct device *dev) | ||||
| 	drm_atomic_helper_shutdown(drm); | ||||
| 	malidp_se_irq_fini(hwdev); | ||||
| 	malidp_de_irq_fini(hwdev); | ||||
| 	drm->irq_enabled = false; | ||||
| 	component_unbind_all(dev, drm); | ||||
| 	of_node_put(malidp->crtc.port); | ||||
| 	malidp->crtc.port = NULL; | ||||
|  | ||||
| @ -95,7 +95,7 @@ static int armada_drm_bind(struct device *dev) | ||||
| 	} | ||||
| 
 | ||||
| 	/* Remove early framebuffers */ | ||||
| 	ret = drm_aperture_remove_framebuffers(false, "armada-drm-fb"); | ||||
| 	ret = drm_aperture_remove_framebuffers(false, &armada_drm_driver); | ||||
| 	if (ret) { | ||||
| 		dev_err(dev, "[" DRM_NAME ":%s] can't kick out simple-fb: %d\n", | ||||
| 			__func__, ret); | ||||
| @ -130,8 +130,6 @@ static int armada_drm_bind(struct device *dev) | ||||
| 	if (ret) | ||||
| 		goto err_comp; | ||||
| 
 | ||||
| 	priv->drm.irq_enabled = true; | ||||
| 
 | ||||
| 	drm_mode_config_reset(&priv->drm); | ||||
| 
 | ||||
| 	ret = armada_fbdev_init(&priv->drm); | ||||
|  | ||||
| @ -247,8 +247,6 @@ static void armada_drm_overlay_plane_atomic_disable(struct drm_plane *plane, | ||||
| } | ||||
| 
 | ||||
| static const struct drm_plane_helper_funcs armada_overlay_plane_helper_funcs = { | ||||
| 	.prepare_fb	= armada_drm_plane_prepare_fb, | ||||
| 	.cleanup_fb	= armada_drm_plane_cleanup_fb, | ||||
| 	.atomic_check	= armada_drm_plane_atomic_check, | ||||
| 	.atomic_update	= armada_drm_overlay_plane_atomic_update, | ||||
| 	.atomic_disable	= armada_drm_overlay_plane_atomic_disable, | ||||
|  | ||||
| @ -78,33 +78,6 @@ void armada_drm_plane_calc(struct drm_plane_state *state, u32 addrs[2][3], | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| int armada_drm_plane_prepare_fb(struct drm_plane *plane, | ||||
| 	struct drm_plane_state *state) | ||||
| { | ||||
| 	DRM_DEBUG_KMS("[PLANE:%d:%s] [FB:%d]\n", | ||||
| 		plane->base.id, plane->name, | ||||
| 		state->fb ? state->fb->base.id : 0); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Take a reference on the new framebuffer - we want to | ||||
| 	 * hold on to it while the hardware is displaying it. | ||||
| 	 */ | ||||
| 	if (state->fb) | ||||
| 		drm_framebuffer_get(state->fb); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| void armada_drm_plane_cleanup_fb(struct drm_plane *plane, | ||||
| 	struct drm_plane_state *old_state) | ||||
| { | ||||
| 	DRM_DEBUG_KMS("[PLANE:%d:%s] [FB:%d]\n", | ||||
| 		plane->base.id, plane->name, | ||||
| 		old_state->fb ? old_state->fb->base.id : 0); | ||||
| 
 | ||||
| 	if (old_state->fb) | ||||
| 		drm_framebuffer_put(old_state->fb); | ||||
| } | ||||
| 
 | ||||
| int armada_drm_plane_atomic_check(struct drm_plane *plane, | ||||
| 	struct drm_atomic_state *state) | ||||
| { | ||||
| @ -282,8 +255,6 @@ static void armada_drm_primary_plane_atomic_disable(struct drm_plane *plane, | ||||
| } | ||||
| 
 | ||||
| static const struct drm_plane_helper_funcs armada_primary_plane_helper_funcs = { | ||||
| 	.prepare_fb	= armada_drm_plane_prepare_fb, | ||||
| 	.cleanup_fb	= armada_drm_plane_cleanup_fb, | ||||
| 	.atomic_check	= armada_drm_plane_atomic_check, | ||||
| 	.atomic_update	= armada_drm_primary_plane_atomic_update, | ||||
| 	.atomic_disable	= armada_drm_primary_plane_atomic_disable, | ||||
|  | ||||
| @ -21,8 +21,6 @@ struct armada_plane_state { | ||||
| 
 | ||||
| void armada_drm_plane_calc(struct drm_plane_state *state, u32 addrs[2][3], | ||||
| 	u16 pitches[3], bool interlaced); | ||||
| int armada_drm_plane_prepare_fb(struct drm_plane *plane, | ||||
| 	struct drm_plane_state *state); | ||||
| void armada_drm_plane_cleanup_fb(struct drm_plane *plane, | ||||
| 	struct drm_plane_state *old_state); | ||||
| int armada_drm_plane_atomic_check(struct drm_plane *plane, | ||||
|  | ||||
| @ -220,7 +220,6 @@ static const struct drm_simple_display_pipe_funcs aspeed_gfx_funcs = { | ||||
| 	.enable		= aspeed_gfx_pipe_enable, | ||||
| 	.disable	= aspeed_gfx_pipe_disable, | ||||
| 	.update		= aspeed_gfx_pipe_update, | ||||
| 	.prepare_fb	= drm_gem_simple_display_pipe_prepare_fb, | ||||
| 	.enable_vblank	= aspeed_gfx_enable_vblank, | ||||
| 	.disable_vblank	= aspeed_gfx_disable_vblank, | ||||
| }; | ||||
|  | ||||
| @ -100,7 +100,7 @@ static int ast_remove_conflicting_framebuffers(struct pci_dev *pdev) | ||||
| 	primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; | ||||
| #endif | ||||
| 
 | ||||
| 	return drm_aperture_remove_conflicting_framebuffers(base, size, primary, "astdrmfb"); | ||||
| 	return drm_aperture_remove_conflicting_framebuffers(base, size, primary, &ast_driver); | ||||
| } | ||||
| 
 | ||||
| static int ast_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | ||||
|  | ||||
| @ -612,8 +612,7 @@ ast_primary_plane_helper_atomic_disable(struct drm_plane *plane, | ||||
| } | ||||
| 
 | ||||
| static const struct drm_plane_helper_funcs ast_primary_plane_helper_funcs = { | ||||
| 	.prepare_fb = drm_gem_vram_plane_helper_prepare_fb, | ||||
| 	.cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, | ||||
| 	DRM_GEM_VRAM_PLANE_HELPER_FUNCS, | ||||
| 	.atomic_check = ast_primary_plane_helper_atomic_check, | ||||
| 	.atomic_update = ast_primary_plane_helper_atomic_update, | ||||
| 	.atomic_disable = ast_primary_plane_helper_atomic_disable, | ||||
| @ -1293,6 +1292,18 @@ static enum drm_mode_status ast_mode_valid(struct drm_connector *connector, | ||||
| 	return flags; | ||||
| } | ||||
| 
 | ||||
| static enum drm_connector_status ast_connector_detect(struct drm_connector | ||||
| 						   *connector, bool force) | ||||
| { | ||||
| 	int r; | ||||
| 
 | ||||
| 	r = ast_get_modes(connector); | ||||
| 	if (r < 0) | ||||
| 		return connector_status_disconnected; | ||||
| 
 | ||||
| 	return connector_status_connected; | ||||
| } | ||||
| 
 | ||||
| static void ast_connector_destroy(struct drm_connector *connector) | ||||
| { | ||||
| 	struct ast_connector *ast_connector = to_ast_connector(connector); | ||||
| @ -1307,6 +1318,7 @@ static const struct drm_connector_helper_funcs ast_connector_helper_funcs = { | ||||
| 
 | ||||
| static const struct drm_connector_funcs ast_connector_funcs = { | ||||
| 	.reset = drm_atomic_helper_connector_reset, | ||||
| 	.detect = ast_connector_detect, | ||||
| 	.fill_modes = drm_helper_probe_single_connector_modes, | ||||
| 	.destroy = ast_connector_destroy, | ||||
| 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | ||||
| @ -1334,7 +1346,8 @@ static int ast_connector_init(struct drm_device *dev) | ||||
| 	connector->interlace_allowed = 0; | ||||
| 	connector->doublescan_allowed = 0; | ||||
| 
 | ||||
| 	connector->polled = DRM_CONNECTOR_POLL_CONNECT; | ||||
| 	connector->polled = DRM_CONNECTOR_POLL_CONNECT | | ||||
| 						DRM_CONNECTOR_POLL_DISCONNECT; | ||||
| 
 | ||||
| 	drm_connector_attach_encoder(connector, encoder); | ||||
| 
 | ||||
| @ -1403,6 +1416,8 @@ int ast_mode_config_init(struct ast_private *ast) | ||||
| 
 | ||||
| 	drm_mode_config_reset(dev); | ||||
| 
 | ||||
| 	drm_kms_helper_poll_init(dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +0,0 @@ | ||||
| # SPDX-License-Identifier: GPL-2.0-only | ||||
| config DRM_BOCHS | ||||
| 	tristate "DRM Support for bochs dispi vga interface (qemu stdvga)" | ||||
| 	depends on DRM && PCI && MMU | ||||
| 	select DRM_KMS_HELPER | ||||
| 	select DRM_VRAM_HELPER | ||||
| 	select DRM_TTM | ||||
| 	select DRM_TTM_HELPER | ||||
| 	help | ||||
| 	  Choose this option for qemu. | ||||
| 	  If M is selected the module will be called bochs-drm. | ||||
| @ -1,4 +0,0 @@ | ||||
| # SPDX-License-Identifier: GPL-2.0-only
 | ||||
| bochs-drm-y := bochs_drv.o bochs_mm.o bochs_kms.o bochs_hw.o | ||||
| 
 | ||||
| obj-$(CONFIG_DRM_BOCHS)	+= bochs-drm.o | ||||
| @ -1,98 +0,0 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0 */ | ||||
| 
 | ||||
| #include <linux/io.h> | ||||
| #include <linux/console.h> | ||||
| 
 | ||||
| #include <drm/drm_crtc.h> | ||||
| #include <drm/drm_crtc_helper.h> | ||||
| #include <drm/drm_encoder.h> | ||||
| #include <drm/drm_fb_helper.h> | ||||
| #include <drm/drm_gem.h> | ||||
| #include <drm/drm_gem_vram_helper.h> | ||||
| #include <drm/drm_simple_kms_helper.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 | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| 
 | ||||
| enum bochs_types { | ||||
| 	BOCHS_QEMU_STDVGA, | ||||
| 	BOCHS_UNKNOWN, | ||||
| }; | ||||
| 
 | ||||
| struct bochs_device { | ||||
| 	/* 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; | ||||
| 	struct edid *edid; | ||||
| 
 | ||||
| 	/* drm */ | ||||
| 	struct drm_device *dev; | ||||
| 	struct drm_simple_display_pipe pipe; | ||||
| 	struct drm_connector connector; | ||||
| }; | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| 
 | ||||
| /* bochs_hw.c */ | ||||
| int bochs_hw_init(struct drm_device *dev); | ||||
| void bochs_hw_fini(struct drm_device *dev); | ||||
| 
 | ||||
| void bochs_hw_blank(struct bochs_device *bochs, bool blank); | ||||
| void bochs_hw_setmode(struct bochs_device *bochs, | ||||
| 		      struct drm_display_mode *mode); | ||||
| void bochs_hw_setformat(struct bochs_device *bochs, | ||||
| 			const struct drm_format_info *format); | ||||
| void bochs_hw_setbase(struct bochs_device *bochs, | ||||
| 		      int x, int y, int stride, u64 addr); | ||||
| int bochs_hw_load_edid(struct bochs_device *bochs); | ||||
| 
 | ||||
| /* bochs_mm.c */ | ||||
| int bochs_mm_init(struct bochs_device *bochs); | ||||
| void bochs_mm_fini(struct bochs_device *bochs); | ||||
| 
 | ||||
| /* bochs_kms.c */ | ||||
| int bochs_kms_init(struct bochs_device *bochs); | ||||
| 
 | ||||
| /* bochs_fbdev.c */ | ||||
| extern const struct drm_mode_config_funcs bochs_mode_funcs; | ||||
| @ -1,205 +0,0 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| /*
 | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/module.h> | ||||
| #include <linux/pci.h> | ||||
| 
 | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_aperture.h> | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_managed.h> | ||||
| 
 | ||||
| #include "bochs.h" | ||||
| 
 | ||||
| static int bochs_modeset = -1; | ||||
| module_param_named(modeset, bochs_modeset, int, 0444); | ||||
| MODULE_PARM_DESC(modeset, "enable/disable kernel modesetting"); | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| /* drm interface                                                          */ | ||||
| 
 | ||||
| static void bochs_unload(struct drm_device *dev) | ||||
| { | ||||
| 	struct bochs_device *bochs = dev->dev_private; | ||||
| 
 | ||||
| 	bochs_mm_fini(bochs); | ||||
| } | ||||
| 
 | ||||
| static int bochs_load(struct drm_device *dev) | ||||
| { | ||||
| 	struct bochs_device *bochs; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	bochs = drmm_kzalloc(dev, sizeof(*bochs), GFP_KERNEL); | ||||
| 	if (bochs == NULL) | ||||
| 		return -ENOMEM; | ||||
| 	dev->dev_private = bochs; | ||||
| 	bochs->dev = dev; | ||||
| 
 | ||||
| 	ret = bochs_hw_init(dev); | ||||
| 	if (ret) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	ret = bochs_mm_init(bochs); | ||||
| 	if (ret) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	ret = bochs_kms_init(bochs); | ||||
| 	if (ret) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err: | ||||
| 	bochs_unload(dev); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| 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_VRAM_DRIVER, | ||||
| 	.release                = bochs_unload, | ||||
| }; | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| /* 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 drm_device *dev; | ||||
| 	unsigned long fbsize; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	fbsize = pci_resource_len(pdev, 0); | ||||
| 	if (fbsize < 4 * 1024 * 1024) { | ||||
| 		DRM_ERROR("less than 4 MB video memory, ignoring device\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "bochsdrmfb"); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	dev = drm_dev_alloc(&bochs_driver, &pdev->dev); | ||||
| 	if (IS_ERR(dev)) | ||||
| 		return PTR_ERR(dev); | ||||
| 
 | ||||
| 	ret = pci_enable_device(pdev); | ||||
| 	if (ret) | ||||
| 		goto err_free_dev; | ||||
| 
 | ||||
| 	pci_set_drvdata(pdev, dev); | ||||
| 
 | ||||
| 	ret = bochs_load(dev); | ||||
| 	if (ret) | ||||
| 		goto err_free_dev; | ||||
| 
 | ||||
| 	ret = drm_dev_register(dev, 0); | ||||
| 	if (ret) | ||||
| 		goto err_unload; | ||||
| 
 | ||||
| 	drm_fbdev_generic_setup(dev, 32); | ||||
| 	return ret; | ||||
| 
 | ||||
| err_unload: | ||||
| 	bochs_unload(dev); | ||||
| 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); | ||||
| 	bochs_hw_fini(dev); | ||||
| 	drm_dev_put(dev); | ||||
| } | ||||
| 
 | ||||
| static const struct pci_device_id bochs_pci_tbl[] = { | ||||
| 	{ | ||||
| 		.vendor      = 0x1234, | ||||
| 		.device      = 0x1111, | ||||
| 		.subvendor   = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, | ||||
| 		.subdevice   = PCI_SUBDEVICE_ID_QEMU, | ||||
| 		.driver_data = BOCHS_QEMU_STDVGA, | ||||
| 	}, | ||||
| 	{ | ||||
| 		.vendor      = 0x1234, | ||||
| 		.device      = 0x1111, | ||||
| 		.subvendor   = PCI_ANY_ID, | ||||
| 		.subdevice   = PCI_ANY_ID, | ||||
| 		.driver_data = BOCHS_UNKNOWN, | ||||
| 	}, | ||||
| 	{ /* end of list */ } | ||||
| }; | ||||
| 
 | ||||
| static struct pci_driver bochs_pci_driver = { | ||||
| 	.name =		"bochs-drm", | ||||
| 	.id_table =	bochs_pci_tbl, | ||||
| 	.probe =	bochs_pci_probe, | ||||
| 	.remove =	bochs_pci_remove, | ||||
| 	.driver.pm =    &bochs_pm_ops, | ||||
| }; | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| /* module init/exit                                                       */ | ||||
| 
 | ||||
| static int __init bochs_init(void) | ||||
| { | ||||
| 	if (vgacon_text_force() && bochs_modeset == -1) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (bochs_modeset == 0) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	return pci_register_driver(&bochs_pci_driver); | ||||
| } | ||||
| 
 | ||||
| static void __exit bochs_exit(void) | ||||
| { | ||||
| 	pci_unregister_driver(&bochs_pci_driver); | ||||
| } | ||||
| 
 | ||||
| module_init(bochs_init); | ||||
| module_exit(bochs_exit); | ||||
| 
 | ||||
| MODULE_DEVICE_TABLE(pci, bochs_pci_tbl); | ||||
| MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| @ -1,323 +0,0 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| /*
 | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/pci.h> | ||||
| 
 | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_fourcc.h> | ||||
| 
 | ||||
| #include <video/vga.h> | ||||
| #include "bochs.h" | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| 
 | ||||
| static void bochs_vga_writeb(struct bochs_device *bochs, u16 ioport, u8 val) | ||||
| { | ||||
| 	if (WARN_ON(ioport < 0x3c0 || ioport > 0x3df)) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (bochs->mmio) { | ||||
| 		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->mmio) { | ||||
| 		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->mmio) { | ||||
| 		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->mmio) { | ||||
| 		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 (start + len > 0x400 /* vga register offset */) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		buf[i] = readb(bochs->mmio + start + i); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int bochs_hw_load_edid(struct bochs_device *bochs) | ||||
| { | ||||
| 	u8 header[8]; | ||||
| 
 | ||||
| 	if (!bochs->mmio) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	/* 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 -1; | ||||
| 
 | ||||
| 	kfree(bochs->edid); | ||||
| 	bochs->edid = drm_do_get_edid(&bochs->connector, | ||||
| 				      bochs_get_edid_block, bochs); | ||||
| 	if (bochs->edid == NULL) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int bochs_hw_init(struct drm_device *dev) | ||||
| { | ||||
| 	struct bochs_device *bochs = dev->dev_private; | ||||
| 	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) { | ||||
| 		/* mmio bar with vga and bochs registers present */ | ||||
| 		if (pci_request_region(pdev, 2, "bochs-drm") != 0) { | ||||
| 			DRM_ERROR("Cannot request mmio region\n"); | ||||
| 			return -EBUSY; | ||||
| 		} | ||||
| 		ioaddr = pci_resource_start(pdev, 2); | ||||
| 		iosize = pci_resource_len(pdev, 2); | ||||
| 		bochs->mmio = ioremap(ioaddr, iosize); | ||||
| 		if (bochs->mmio == NULL) { | ||||
| 			DRM_ERROR("Cannot map mmio region\n"); | ||||
| 			return -ENOMEM; | ||||
| 		} | ||||
| 	} else { | ||||
| 		ioaddr = VBE_DISPI_IOPORT_INDEX; | ||||
| 		iosize = 2; | ||||
| 		if (!request_region(ioaddr, iosize, "bochs-drm")) { | ||||
| 			DRM_ERROR("Cannot request ioports\n"); | ||||
| 			return -EBUSY; | ||||
| 		} | ||||
| 		bochs->ioports = 1; | ||||
| 	} | ||||
| 
 | ||||
| 	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 (pci_request_region(pdev, 0, "bochs-drm") != 0) | ||||
| 		DRM_WARN("Cannot request framebuffer, boot fb still active?\n"); | ||||
| 
 | ||||
| 	bochs->fb_map = ioremap(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; | ||||
| } | ||||
| 
 | ||||
| void bochs_hw_fini(struct drm_device *dev) | ||||
| { | ||||
| 	struct bochs_device *bochs = dev->dev_private; | ||||
| 
 | ||||
| 	/* TODO: shot down existing vram mappings */ | ||||
| 
 | ||||
| 	if (bochs->mmio) | ||||
| 		iounmap(bochs->mmio); | ||||
| 	if (bochs->ioports) | ||||
| 		release_region(VBE_DISPI_IOPORT_INDEX, 2); | ||||
| 	if (bochs->fb_map) | ||||
| 		iounmap(bochs->fb_map); | ||||
| 	pci_release_regions(to_pci_dev(dev->dev)); | ||||
| 	kfree(bochs->edid); | ||||
| } | ||||
| 
 | ||||
| void bochs_hw_blank(struct bochs_device *bochs, bool blank) | ||||
| { | ||||
| 	DRM_DEBUG_DRIVER("hw_blank %d\n", blank); | ||||
| 	/* 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); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| @ -1,178 +0,0 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| /*
 | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/moduleparam.h> | ||||
| 
 | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_gem_framebuffer_helper.h> | ||||
| #include <drm/drm_probe_helper.h> | ||||
| 
 | ||||
| #include "bochs.h" | ||||
| 
 | ||||
| static int defx = 1024; | ||||
| static int defy = 768; | ||||
| 
 | ||||
| module_param(defx, int, 0444); | ||||
| module_param(defy, int, 0444); | ||||
| MODULE_PARM_DESC(defx, "default x resolution"); | ||||
| MODULE_PARM_DESC(defy, "default y resolution"); | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| 
 | ||||
| static const uint32_t bochs_formats[] = { | ||||
| 	DRM_FORMAT_XRGB8888, | ||||
| 	DRM_FORMAT_BGRX8888, | ||||
| }; | ||||
| 
 | ||||
| static void bochs_plane_update(struct bochs_device *bochs, | ||||
| 			       struct drm_plane_state *state) | ||||
| { | ||||
| 	struct drm_gem_vram_object *gbo; | ||||
| 	s64 gpu_addr; | ||||
| 
 | ||||
| 	if (!state->fb || !bochs->stride) | ||||
| 		return; | ||||
| 
 | ||||
| 	gbo = drm_gem_vram_of_gem(state->fb->obj[0]); | ||||
| 	gpu_addr = drm_gem_vram_offset(gbo); | ||||
| 	if (WARN_ON_ONCE(gpu_addr < 0)) | ||||
| 		return; /* Bug: we didn't pin the BO to VRAM in prepare_fb. */ | ||||
| 
 | ||||
| 	bochs_hw_setbase(bochs, | ||||
| 			 state->crtc_x, | ||||
| 			 state->crtc_y, | ||||
| 			 state->fb->pitches[0], | ||||
| 			 state->fb->offsets[0] + gpu_addr); | ||||
| 	bochs_hw_setformat(bochs, state->fb->format); | ||||
| } | ||||
| 
 | ||||
| static void bochs_pipe_enable(struct drm_simple_display_pipe *pipe, | ||||
| 			      struct drm_crtc_state *crtc_state, | ||||
| 			      struct drm_plane_state *plane_state) | ||||
| { | ||||
| 	struct bochs_device *bochs = pipe->crtc.dev->dev_private; | ||||
| 
 | ||||
| 	bochs_hw_setmode(bochs, &crtc_state->mode); | ||||
| 	bochs_plane_update(bochs, plane_state); | ||||
| } | ||||
| 
 | ||||
| static void bochs_pipe_disable(struct drm_simple_display_pipe *pipe) | ||||
| { | ||||
| 	struct bochs_device *bochs = pipe->crtc.dev->dev_private; | ||||
| 
 | ||||
| 	bochs_hw_blank(bochs, true); | ||||
| } | ||||
| 
 | ||||
| static void bochs_pipe_update(struct drm_simple_display_pipe *pipe, | ||||
| 			      struct drm_plane_state *old_state) | ||||
| { | ||||
| 	struct bochs_device *bochs = pipe->crtc.dev->dev_private; | ||||
| 
 | ||||
| 	bochs_plane_update(bochs, pipe->plane.state); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_simple_display_pipe_funcs bochs_pipe_funcs = { | ||||
| 	.enable	    = bochs_pipe_enable, | ||||
| 	.disable    = bochs_pipe_disable, | ||||
| 	.update	    = bochs_pipe_update, | ||||
| 	.prepare_fb = drm_gem_vram_simple_display_pipe_prepare_fb, | ||||
| 	.cleanup_fb = drm_gem_vram_simple_display_pipe_cleanup_fb, | ||||
| }; | ||||
| 
 | ||||
| static int bochs_connector_get_modes(struct drm_connector *connector) | ||||
| { | ||||
| 	struct bochs_device *bochs = | ||||
| 		container_of(connector, struct bochs_device, connector); | ||||
| 	int count = 0; | ||||
| 
 | ||||
| 	if (bochs->edid) | ||||
| 		count = drm_add_edid_modes(connector, bochs->edid); | ||||
| 
 | ||||
| 	if (!count) { | ||||
| 		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_connector_helper_funcs = { | ||||
| 	.get_modes = bochs_connector_get_modes, | ||||
| }; | ||||
| 
 | ||||
| static const struct drm_connector_funcs bochs_connector_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 void bochs_connector_init(struct drm_device *dev) | ||||
| { | ||||
| 	struct bochs_device *bochs = dev->dev_private; | ||||
| 	struct drm_connector *connector = &bochs->connector; | ||||
| 
 | ||||
| 	drm_connector_init(dev, connector, &bochs_connector_connector_funcs, | ||||
| 			   DRM_MODE_CONNECTOR_VIRTUAL); | ||||
| 	drm_connector_helper_add(connector, | ||||
| 				 &bochs_connector_connector_helper_funcs); | ||||
| 
 | ||||
| 	bochs_hw_load_edid(bochs); | ||||
| 	if (bochs->edid) { | ||||
| 		DRM_INFO("Found EDID data blob.\n"); | ||||
| 		drm_connector_attach_edid_property(connector); | ||||
| 		drm_connector_update_edid_property(connector, bochs->edid); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static struct drm_framebuffer * | ||||
| bochs_gem_fb_create(struct drm_device *dev, struct drm_file *file, | ||||
| 		    const struct drm_mode_fb_cmd2 *mode_cmd) | ||||
| { | ||||
| 	if (mode_cmd->pixel_format != DRM_FORMAT_XRGB8888 && | ||||
| 	    mode_cmd->pixel_format != DRM_FORMAT_BGRX8888) | ||||
| 		return ERR_PTR(-EINVAL); | ||||
| 
 | ||||
| 	return drm_gem_fb_create(dev, file, mode_cmd); | ||||
| } | ||||
| 
 | ||||
| const struct drm_mode_config_funcs bochs_mode_funcs = { | ||||
| 	.fb_create = bochs_gem_fb_create, | ||||
| 	.mode_valid = drm_vram_helper_mode_valid, | ||||
| 	.atomic_check = drm_atomic_helper_check, | ||||
| 	.atomic_commit = drm_atomic_helper_commit, | ||||
| }; | ||||
| 
 | ||||
| int bochs_kms_init(struct bochs_device *bochs) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = drmm_mode_config_init(bochs->dev); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	bochs->dev->mode_config.max_width = 8192; | ||||
| 	bochs->dev->mode_config.max_height = 8192; | ||||
| 
 | ||||
| 	bochs->dev->mode_config.fb_base = bochs->fb_base; | ||||
| 	bochs->dev->mode_config.preferred_depth = 24; | ||||
| 	bochs->dev->mode_config.prefer_shadow = 0; | ||||
| 	bochs->dev->mode_config.prefer_shadow_fbdev = 1; | ||||
| 	bochs->dev->mode_config.quirk_addfb_prefer_host_byte_order = true; | ||||
| 
 | ||||
| 	bochs->dev->mode_config.funcs = &bochs_mode_funcs; | ||||
| 
 | ||||
| 	bochs_connector_init(bochs->dev); | ||||
| 	drm_simple_display_pipe_init(bochs->dev, | ||||
| 				     &bochs->pipe, | ||||
| 				     &bochs_pipe_funcs, | ||||
| 				     bochs_formats, | ||||
| 				     ARRAY_SIZE(bochs_formats), | ||||
| 				     NULL, | ||||
| 				     &bochs->connector); | ||||
| 
 | ||||
| 	drm_mode_config_reset(bochs->dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| /*
 | ||||
|  */ | ||||
| 
 | ||||
| #include "bochs.h" | ||||
| 
 | ||||
| /* ---------------------------------------------------------------------- */ | ||||
| 
 | ||||
| int bochs_mm_init(struct bochs_device *bochs) | ||||
| { | ||||
| 	struct drm_vram_mm *vmm; | ||||
| 
 | ||||
| 	vmm = drm_vram_helper_alloc_mm(bochs->dev, bochs->fb_base, | ||||
| 				       bochs->fb_size); | ||||
| 	return PTR_ERR_OR_ZERO(vmm); | ||||
| } | ||||
| 
 | ||||
| void bochs_mm_fini(struct bochs_device *bochs) | ||||
| { | ||||
| 	if (!bochs->dev->vram_mm) | ||||
| 		return; | ||||
| 
 | ||||
| 	drm_vram_helper_release_mm(bochs->dev); | ||||
| } | ||||
| @ -303,6 +303,7 @@ config DRM_TI_SN65DSI86 | ||||
| 	select DRM_PANEL | ||||
| 	select DRM_MIPI_DSI | ||||
| 	select AUXILIARY_BUS | ||||
| 	select DRM_DP_AUX_BUS | ||||
| 	help | ||||
| 	  Texas Instruments SN65DSI86 DSI to eDP Bridge driver | ||||
| 
 | ||||
|  | ||||
| @ -1730,7 +1730,6 @@ static int __maybe_unused anx7625_suspend(struct device *dev) | ||||
| 	if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { | ||||
| 		anx7625_runtime_pm_suspend(dev); | ||||
| 		disable_irq(ctx->pdata.intp_irq); | ||||
| 		flush_workqueue(ctx->workqueue); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| @ -1790,7 +1789,8 @@ static int anx7625_i2c_probe(struct i2c_client *client, | ||||
| 	platform->pdata.intp_irq = client->irq; | ||||
| 	if (platform->pdata.intp_irq) { | ||||
| 		INIT_WORK(&platform->work, anx7625_work_func); | ||||
| 		platform->workqueue = create_workqueue("anx7625_work"); | ||||
| 		platform->workqueue = alloc_workqueue("anx7625_work", | ||||
| 						      WQ_FREEZABLE | WQ_MEM_RECLAIM, 1); | ||||
| 		if (!platform->workqueue) { | ||||
| 			DRM_DEV_ERROR(dev, "fail to create work queue\n"); | ||||
| 			ret = -ENOMEM; | ||||
| @ -1874,6 +1874,7 @@ static const struct of_device_id anx_match_table[] = { | ||||
| 	{.compatible = "analogix,anx7625",}, | ||||
| 	{}, | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(of, anx_match_table); | ||||
| 
 | ||||
| static struct i2c_driver anx7625_driver = { | ||||
| 	.driver = { | ||||
|  | ||||
| @ -48,12 +48,6 @@ enum transfer_direction { | ||||
| #define NWL_DSI_ENDPOINT_LCDIF 0 | ||||
| #define NWL_DSI_ENDPOINT_DCSS 1 | ||||
| 
 | ||||
| struct nwl_dsi_plat_clk_config { | ||||
| 	const char *id; | ||||
| 	struct clk *clk; | ||||
| 	bool present; | ||||
| }; | ||||
| 
 | ||||
| struct nwl_dsi_transfer { | ||||
| 	const struct mipi_dsi_msg *msg; | ||||
| 	struct mipi_dsi_packet packet; | ||||
|  | ||||
| @ -137,7 +137,6 @@ enum sn65dsi83_model { | ||||
| 
 | ||||
| struct sn65dsi83 { | ||||
| 	struct drm_bridge		bridge; | ||||
| 	struct drm_display_mode		mode; | ||||
| 	struct device			*dev; | ||||
| 	struct regmap			*regmap; | ||||
| 	struct device_node		*host_node; | ||||
| @ -147,8 +146,6 @@ struct sn65dsi83 { | ||||
| 	int				dsi_lanes; | ||||
| 	bool				lvds_dual_link; | ||||
| 	bool				lvds_dual_link_even_odd_swap; | ||||
| 	bool				lvds_format_24bpp; | ||||
| 	bool				lvds_format_jeida; | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_range sn65dsi83_readable_ranges[] = { | ||||
| @ -291,7 +288,8 @@ err_dsi_attach: | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void sn65dsi83_pre_enable(struct drm_bridge *bridge) | ||||
| static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge, | ||||
| 					struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); | ||||
| 
 | ||||
| @ -306,7 +304,8 @@ static void sn65dsi83_pre_enable(struct drm_bridge *bridge) | ||||
| 	usleep_range(1000, 1100); | ||||
| } | ||||
| 
 | ||||
| static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx) | ||||
| static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx, | ||||
| 				   const struct drm_display_mode *mode) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * The encoding of the LVDS_CLK_RANGE is as follows: | ||||
| @ -322,7 +321,7 @@ static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx) | ||||
| 	 * the clock to 25..154 MHz, the range calculation can be simplified | ||||
| 	 * as follows: | ||||
| 	 */ | ||||
| 	int mode_clock = ctx->mode.clock; | ||||
| 	int mode_clock = mode->clock; | ||||
| 
 | ||||
| 	if (ctx->lvds_dual_link) | ||||
| 		mode_clock /= 2; | ||||
| @ -330,7 +329,8 @@ static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx) | ||||
| 	return (mode_clock - 12500) / 25000; | ||||
| } | ||||
| 
 | ||||
| static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx) | ||||
| static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx, | ||||
| 				  const struct drm_display_mode *mode) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * The encoding of the CHA_DSI_CLK_RANGE is as follows: | ||||
| @ -346,7 +346,7 @@ static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx) | ||||
| 	 *  DSI_CLK = mode clock * bpp / dsi_data_lanes / 2 | ||||
| 	 * the 2 is there because the bus is DDR. | ||||
| 	 */ | ||||
| 	return DIV_ROUND_UP(clamp((unsigned int)ctx->mode.clock * | ||||
| 	return DIV_ROUND_UP(clamp((unsigned int)mode->clock * | ||||
| 			    mipi_dsi_pixel_format_to_bpp(ctx->dsi->format) / | ||||
| 			    ctx->dsi_lanes / 2, 40000U, 500000U), 5000U); | ||||
| } | ||||
| @ -364,23 +364,73 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx) | ||||
| 	return dsi_div - 1; | ||||
| } | ||||
| 
 | ||||
| static void sn65dsi83_enable(struct drm_bridge *bridge) | ||||
| static void sn65dsi83_atomic_enable(struct drm_bridge *bridge, | ||||
| 				    struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); | ||||
| 	struct drm_atomic_state *state = old_bridge_state->base.state; | ||||
| 	const struct drm_bridge_state *bridge_state; | ||||
| 	const struct drm_crtc_state *crtc_state; | ||||
| 	const struct drm_display_mode *mode; | ||||
| 	struct drm_connector *connector; | ||||
| 	struct drm_crtc *crtc; | ||||
| 	bool lvds_format_24bpp; | ||||
| 	bool lvds_format_jeida; | ||||
| 	unsigned int pval; | ||||
| 	__le16 le16val; | ||||
| 	u16 val; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Get the LVDS format from the bridge state. */ | ||||
| 	bridge_state = drm_atomic_get_new_bridge_state(state, bridge); | ||||
| 
 | ||||
| 	switch (bridge_state->output_bus_cfg.format) { | ||||
| 	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: | ||||
| 		lvds_format_24bpp = false; | ||||
| 		lvds_format_jeida = true; | ||||
| 		break; | ||||
| 	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: | ||||
| 		lvds_format_24bpp = true; | ||||
| 		lvds_format_jeida = true; | ||||
| 		break; | ||||
| 	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: | ||||
| 		lvds_format_24bpp = true; | ||||
| 		lvds_format_jeida = false; | ||||
| 		break; | ||||
| 	default: | ||||
| 		/*
 | ||||
| 		 * Some bridges still don't set the correct | ||||
| 		 * LVDS bus pixel format, use SPWG24 default | ||||
| 		 * format until those are fixed. | ||||
| 		 */ | ||||
| 		lvds_format_24bpp = true; | ||||
| 		lvds_format_jeida = false; | ||||
| 		dev_warn(ctx->dev, | ||||
| 			 "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", | ||||
| 			 bridge_state->output_bus_cfg.format); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Retrieve the CRTC adjusted mode. This requires a little dance to go | ||||
| 	 * from the bridge to the encoder, to the connector and to the CRTC. | ||||
| 	 */ | ||||
| 	connector = drm_atomic_get_new_connector_for_encoder(state, | ||||
| 							     bridge->encoder); | ||||
| 	crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; | ||||
| 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc); | ||||
| 	mode = &crtc_state->adjusted_mode; | ||||
| 
 | ||||
| 	/* Clear reset, disable PLL */ | ||||
| 	regmap_write(ctx->regmap, REG_RC_RESET, 0x00); | ||||
| 	regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00); | ||||
| 
 | ||||
| 	/* Reference clock derived from DSI link clock. */ | ||||
| 	regmap_write(ctx->regmap, REG_RC_LVDS_PLL, | ||||
| 		     REG_RC_LVDS_PLL_LVDS_CLK_RANGE(sn65dsi83_get_lvds_range(ctx)) | | ||||
| 		     REG_RC_LVDS_PLL_LVDS_CLK_RANGE(sn65dsi83_get_lvds_range(ctx, mode)) | | ||||
| 		     REG_RC_LVDS_PLL_HS_CLK_SRC_DPHY); | ||||
| 	regmap_write(ctx->regmap, REG_DSI_CLK, | ||||
| 		     REG_DSI_CLK_CHA_DSI_CLK_RANGE(sn65dsi83_get_dsi_range(ctx))); | ||||
| 		     REG_DSI_CLK_CHA_DSI_CLK_RANGE(sn65dsi83_get_dsi_range(ctx, mode))); | ||||
| 	regmap_write(ctx->regmap, REG_RC_DSI_CLK, | ||||
| 		     REG_RC_DSI_CLK_DSI_CLK_DIVIDER(sn65dsi83_get_dsi_div(ctx))); | ||||
| 
 | ||||
| @ -394,20 +444,20 @@ static void sn65dsi83_enable(struct drm_bridge *bridge) | ||||
| 	regmap_write(ctx->regmap, REG_DSI_EQ, 0x00); | ||||
| 
 | ||||
| 	/* Set up sync signal polarity. */ | ||||
| 	val = (ctx->mode.flags & DRM_MODE_FLAG_NHSYNC ? | ||||
| 	val = (mode->flags & DRM_MODE_FLAG_NHSYNC ? | ||||
| 	       REG_LVDS_FMT_HS_NEG_POLARITY : 0) | | ||||
| 	      (ctx->mode.flags & DRM_MODE_FLAG_NVSYNC ? | ||||
| 	      (mode->flags & DRM_MODE_FLAG_NVSYNC ? | ||||
| 	       REG_LVDS_FMT_VS_NEG_POLARITY : 0); | ||||
| 
 | ||||
| 	/* Set up bits-per-pixel, 18bpp or 24bpp. */ | ||||
| 	if (ctx->lvds_format_24bpp) { | ||||
| 	if (lvds_format_24bpp) { | ||||
| 		val |= REG_LVDS_FMT_CHA_24BPP_MODE; | ||||
| 		if (ctx->lvds_dual_link) | ||||
| 			val |= REG_LVDS_FMT_CHB_24BPP_MODE; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Set up LVDS format, JEIDA/Format 1 or SPWG/Format 2 */ | ||||
| 	if (ctx->lvds_format_jeida) { | ||||
| 	if (lvds_format_jeida) { | ||||
| 		val |= REG_LVDS_FMT_CHA_24BPP_FORMAT1; | ||||
| 		if (ctx->lvds_dual_link) | ||||
| 			val |= REG_LVDS_FMT_CHB_24BPP_FORMAT1; | ||||
| @ -426,29 +476,29 @@ static void sn65dsi83_enable(struct drm_bridge *bridge) | ||||
| 		     REG_LVDS_LANE_CHB_LVDS_TERM); | ||||
| 	regmap_write(ctx->regmap, REG_LVDS_CM, 0x00); | ||||
| 
 | ||||
| 	val = cpu_to_le16(ctx->mode.hdisplay); | ||||
| 	le16val = cpu_to_le16(mode->hdisplay); | ||||
| 	regmap_bulk_write(ctx->regmap, REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW, | ||||
| 			  &val, 2); | ||||
| 	val = cpu_to_le16(ctx->mode.vdisplay); | ||||
| 			  &le16val, 2); | ||||
| 	le16val = cpu_to_le16(mode->vdisplay); | ||||
| 	regmap_bulk_write(ctx->regmap, REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW, | ||||
| 			  &val, 2); | ||||
| 			  &le16val, 2); | ||||
| 	/* 32 + 1 pixel clock to ensure proper operation */ | ||||
| 	val = cpu_to_le16(32 + 1); | ||||
| 	regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &val, 2); | ||||
| 	val = cpu_to_le16(ctx->mode.hsync_end - ctx->mode.hsync_start); | ||||
| 	le16val = cpu_to_le16(32 + 1); | ||||
| 	regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &le16val, 2); | ||||
| 	le16val = cpu_to_le16(mode->hsync_end - mode->hsync_start); | ||||
| 	regmap_bulk_write(ctx->regmap, REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW, | ||||
| 			  &val, 2); | ||||
| 	val = cpu_to_le16(ctx->mode.vsync_end - ctx->mode.vsync_start); | ||||
| 			  &le16val, 2); | ||||
| 	le16val = cpu_to_le16(mode->vsync_end - mode->vsync_start); | ||||
| 	regmap_bulk_write(ctx->regmap, REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW, | ||||
| 			  &val, 2); | ||||
| 			  &le16val, 2); | ||||
| 	regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_BACK_PORCH, | ||||
| 		     ctx->mode.htotal - ctx->mode.hsync_end); | ||||
| 		     mode->htotal - mode->hsync_end); | ||||
| 	regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_BACK_PORCH, | ||||
| 		     ctx->mode.vtotal - ctx->mode.vsync_end); | ||||
| 		     mode->vtotal - mode->vsync_end); | ||||
| 	regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_FRONT_PORCH, | ||||
| 		     ctx->mode.hsync_start - ctx->mode.hdisplay); | ||||
| 		     mode->hsync_start - mode->hdisplay); | ||||
| 	regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_FRONT_PORCH, | ||||
| 		     ctx->mode.vsync_start - ctx->mode.vdisplay); | ||||
| 		     mode->vsync_start - mode->vdisplay); | ||||
| 	regmap_write(ctx->regmap, REG_VID_CHA_TEST_PATTERN, 0x00); | ||||
| 
 | ||||
| 	/* Enable PLL */ | ||||
| @ -472,7 +522,8 @@ static void sn65dsi83_enable(struct drm_bridge *bridge) | ||||
| 	regmap_write(ctx->regmap, REG_IRQ_STAT, pval); | ||||
| } | ||||
| 
 | ||||
| static void sn65dsi83_disable(struct drm_bridge *bridge) | ||||
| static void sn65dsi83_atomic_disable(struct drm_bridge *bridge, | ||||
| 				     struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); | ||||
| 
 | ||||
| @ -481,7 +532,8 @@ static void sn65dsi83_disable(struct drm_bridge *bridge) | ||||
| 	regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00); | ||||
| } | ||||
| 
 | ||||
| static void sn65dsi83_post_disable(struct drm_bridge *bridge) | ||||
| static void sn65dsi83_atomic_post_disable(struct drm_bridge *bridge, | ||||
| 					  struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); | ||||
| 
 | ||||
| @ -503,70 +555,44 @@ sn65dsi83_mode_valid(struct drm_bridge *bridge, | ||||
| 	return MODE_OK; | ||||
| } | ||||
| 
 | ||||
| static void sn65dsi83_mode_set(struct drm_bridge *bridge, | ||||
| 			       const struct drm_display_mode *mode, | ||||
| 			       const struct drm_display_mode *adj) | ||||
| #define MAX_INPUT_SEL_FORMATS	1 | ||||
| 
 | ||||
| static u32 * | ||||
| sn65dsi83_atomic_get_input_bus_fmts(struct drm_bridge *bridge, | ||||
| 				    struct drm_bridge_state *bridge_state, | ||||
| 				    struct drm_crtc_state *crtc_state, | ||||
| 				    struct drm_connector_state *conn_state, | ||||
| 				    u32 output_fmt, | ||||
| 				    unsigned int *num_input_fmts) | ||||
| { | ||||
| 	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); | ||||
| 	u32 *input_fmts; | ||||
| 
 | ||||
| 	ctx->mode = *adj; | ||||
| } | ||||
| 	*num_input_fmts = 0; | ||||
| 
 | ||||
| static bool sn65dsi83_mode_fixup(struct drm_bridge *bridge, | ||||
| 				 const struct drm_display_mode *mode, | ||||
| 				 struct drm_display_mode *adj) | ||||
| { | ||||
| 	struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); | ||||
| 	u32 input_bus_format = MEDIA_BUS_FMT_RGB888_1X24; | ||||
| 	struct drm_encoder *encoder = bridge->encoder; | ||||
| 	struct drm_device *ddev = encoder->dev; | ||||
| 	struct drm_connector *connector; | ||||
| 	input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), | ||||
| 			     GFP_KERNEL); | ||||
| 	if (!input_fmts) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	/* The DSI format is always RGB888_1X24 */ | ||||
| 	list_for_each_entry(connector, &ddev->mode_config.connector_list, head) { | ||||
| 		switch (connector->display_info.bus_formats[0]) { | ||||
| 		case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: | ||||
| 			ctx->lvds_format_24bpp = false; | ||||
| 			ctx->lvds_format_jeida = true; | ||||
| 			break; | ||||
| 		case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: | ||||
| 			ctx->lvds_format_24bpp = true; | ||||
| 			ctx->lvds_format_jeida = true; | ||||
| 			break; | ||||
| 		case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: | ||||
| 			ctx->lvds_format_24bpp = true; | ||||
| 			ctx->lvds_format_jeida = false; | ||||
| 			break; | ||||
| 		default: | ||||
| 			/*
 | ||||
| 			 * Some bridges still don't set the correct | ||||
| 			 * LVDS bus pixel format, use SPWG24 default | ||||
| 			 * format until those are fixed. | ||||
| 			 */ | ||||
| 			ctx->lvds_format_24bpp = true; | ||||
| 			ctx->lvds_format_jeida = false; | ||||
| 			dev_warn(ctx->dev, | ||||
| 				 "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", | ||||
| 				 connector->display_info.bus_formats[0]); | ||||
| 			break; | ||||
| 		} | ||||
| 	/* This is the DSI-end bus format */ | ||||
| 	input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; | ||||
| 	*num_input_fmts = 1; | ||||
| 
 | ||||
| 		drm_display_info_set_bus_formats(&connector->display_info, | ||||
| 						 &input_bus_format, 1); | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| 	return input_fmts; | ||||
| } | ||||
| 
 | ||||
| static const struct drm_bridge_funcs sn65dsi83_funcs = { | ||||
| 	.attach		= sn65dsi83_attach, | ||||
| 	.pre_enable	= sn65dsi83_pre_enable, | ||||
| 	.enable		= sn65dsi83_enable, | ||||
| 	.disable	= sn65dsi83_disable, | ||||
| 	.post_disable	= sn65dsi83_post_disable, | ||||
| 	.mode_valid	= sn65dsi83_mode_valid, | ||||
| 	.mode_set	= sn65dsi83_mode_set, | ||||
| 	.mode_fixup	= sn65dsi83_mode_fixup, | ||||
| 	.attach			= sn65dsi83_attach, | ||||
| 	.atomic_pre_enable	= sn65dsi83_atomic_pre_enable, | ||||
| 	.atomic_enable		= sn65dsi83_atomic_enable, | ||||
| 	.atomic_disable		= sn65dsi83_atomic_disable, | ||||
| 	.atomic_post_disable	= sn65dsi83_atomic_post_disable, | ||||
| 	.mode_valid		= sn65dsi83_mode_valid, | ||||
| 
 | ||||
| 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, | ||||
| 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, | ||||
| 	.atomic_reset = drm_atomic_helper_bridge_reset, | ||||
| 	.atomic_get_input_bus_fmts = sn65dsi83_atomic_get_input_bus_fmts, | ||||
| }; | ||||
| 
 | ||||
| static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -33,6 +33,10 @@ | ||||
|  * | ||||
|  * .. code-block:: c | ||||
|  * | ||||
|  *	static const struct drm_driver example_driver = { | ||||
|  *		... | ||||
|  *	}; | ||||
|  * | ||||
|  *	static int remove_conflicting_framebuffers(struct pci_dev *pdev) | ||||
|  *	{ | ||||
|  *		bool primary = false; | ||||
| @ -46,7 +50,7 @@ | ||||
|  *	#endif | ||||
|  * | ||||
|  *		return drm_aperture_remove_conflicting_framebuffers(base, size, primary, | ||||
|  *		                                                    "example driver"); | ||||
|  *		                                                    &example_driver); | ||||
|  *	} | ||||
|  * | ||||
|  *	static int probe(struct pci_dev *pdev) | ||||
| @ -274,7 +278,7 @@ static void drm_aperture_detach_drivers(resource_size_t base, resource_size_t si | ||||
|  * @base: the aperture's base address in physical memory | ||||
|  * @size: aperture size in bytes | ||||
|  * @primary: also kick vga16fb if present | ||||
|  * @name: requesting driver name | ||||
|  * @req_driver: requesting DRM driver | ||||
|  * | ||||
|  * This function removes graphics device drivers which use memory range described by | ||||
|  * @base and @size. | ||||
| @ -283,7 +287,7 @@ static void drm_aperture_detach_drivers(resource_size_t base, resource_size_t si | ||||
|  * 0 on success, or a negative errno code otherwise | ||||
|  */ | ||||
| int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_size_t size, | ||||
| 						 bool primary, const char *name) | ||||
| 						 bool primary, const struct drm_driver *req_driver) | ||||
| { | ||||
| #if IS_REACHABLE(CONFIG_FB) | ||||
| 	struct apertures_struct *a; | ||||
| @ -296,7 +300,7 @@ int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_ | ||||
| 	a->ranges[0].base = base; | ||||
| 	a->ranges[0].size = size; | ||||
| 
 | ||||
| 	ret = remove_conflicting_framebuffers(a, name, primary); | ||||
| 	ret = remove_conflicting_framebuffers(a, req_driver->name, primary); | ||||
| 	kfree(a); | ||||
| 
 | ||||
| 	if (ret) | ||||
| @ -312,7 +316,7 @@ EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers); | ||||
| /**
 | ||||
|  * drm_aperture_remove_conflicting_pci_framebuffers - remove existing framebuffers for PCI devices | ||||
|  * @pdev: PCI device | ||||
|  * @name: requesting driver name | ||||
|  * @req_driver: requesting DRM driver | ||||
|  * | ||||
|  * This function removes graphics device drivers using memory range configured | ||||
|  * for any of @pdev's memory bars. The function assumes that PCI device with | ||||
| @ -321,7 +325,8 @@ EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers); | ||||
|  * Returns: | ||||
|  * 0 on success, or a negative errno code otherwise | ||||
|  */ | ||||
| int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, const char *name) | ||||
| int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, | ||||
| 						     const struct drm_driver *req_driver) | ||||
| { | ||||
| 	resource_size_t base, size; | ||||
| 	int bar, ret = 0; | ||||
| @ -339,7 +344,7 @@ int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, const | ||||
| 	 * otherwise the vga fbdev driver falls over. | ||||
| 	 */ | ||||
| #if IS_REACHABLE(CONFIG_FB) | ||||
| 	ret = remove_conflicting_pci_framebuffers(pdev, name); | ||||
| 	ret = remove_conflicting_pci_framebuffers(pdev, req_driver->name); | ||||
| #endif | ||||
| 	if (ret == 0) | ||||
| 		ret = vga_remove_vgacon(pdev); | ||||
|  | ||||
| @ -35,6 +35,7 @@ | ||||
| #include <drm/drm_damage_helper.h> | ||||
| #include <drm/drm_device.h> | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_gem_atomic_helper.h> | ||||
| #include <drm/drm_plane_helper.h> | ||||
| #include <drm/drm_print.h> | ||||
| #include <drm/drm_self_refresh_helper.h> | ||||
| @ -2405,6 +2406,15 @@ int drm_atomic_helper_prepare_planes(struct drm_device *dev, | ||||
| 			ret = funcs->prepare_fb(plane, new_plane_state); | ||||
| 			if (ret) | ||||
| 				goto fail; | ||||
| 		} else { | ||||
| 			WARN_ON_ONCE(funcs->cleanup_fb); | ||||
| 
 | ||||
| 			if (!drm_core_check_feature(dev, DRIVER_GEM)) | ||||
| 				continue; | ||||
| 
 | ||||
| 			ret = drm_gem_plane_helper_prepare_fb(plane, new_plane_state); | ||||
| 			if (ret) | ||||
| 				goto fail; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -46,10 +46,10 @@ | ||||
|  * it reached a given hardware component (a CRC sampling "source"). | ||||
|  * | ||||
|  * Userspace can control generation of CRCs in a given CRTC by writing to the | ||||
|  * file dri/0/crtc-N/crc/control in debugfs, with N being the index of the CRTC. | ||||
|  * Accepted values are source names (which are driver-specific) and the "auto" | ||||
|  * keyword, which will let the driver select a default source of frame CRCs | ||||
|  * for this CRTC. | ||||
|  * file dri/0/crtc-N/crc/control in debugfs, with N being the :ref:`index of | ||||
|  * the CRTC<crtc_index>`. Accepted values are source names (which are | ||||
|  * driver-specific) and the "auto" keyword, which will let the driver select a | ||||
|  * default source of frame CRCs for this CRTC. | ||||
|  * | ||||
|  * Once frame CRC generation is enabled, userspace can capture them by reading | ||||
|  * the dri/0/crtc-N/crc/data file. Each line in that file contains the frame | ||||
|  | ||||
							
								
								
									
										326
									
								
								drivers/gpu/drm/drm_dp_aux_bus.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								drivers/gpu/drm/drm_dp_aux_bus.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,326 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| /*
 | ||||
|  * Copyright 2021 Google Inc. | ||||
|  * | ||||
|  * The DP AUX bus is used for devices that are connected over a DisplayPort | ||||
|  * AUX bus. The devices on the far side of the bus are referred to as | ||||
|  * endpoints in this code. | ||||
|  * | ||||
|  * Commonly there is only one device connected to the DP AUX bus: a panel. | ||||
|  * Though historically panels (even DP panels) have been modeled as simple | ||||
|  * platform devices, putting them under the DP AUX bus allows the panel driver | ||||
|  * to perform transactions on that bus. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/init.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/of_device.h> | ||||
| #include <linux/pm_domain.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| 
 | ||||
| #include <drm/drm_dp_aux_bus.h> | ||||
| #include <drm/drm_dp_helper.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * dp_aux_ep_match() - The match function for the dp_aux_bus. | ||||
|  * @dev: The device to match. | ||||
|  * @drv: The driver to try to match against. | ||||
|  * | ||||
|  * At the moment, we just match on device tree. | ||||
|  * | ||||
|  * Return: True if this driver matches this device; false otherwise. | ||||
|  */ | ||||
| static int dp_aux_ep_match(struct device *dev, struct device_driver *drv) | ||||
| { | ||||
| 	return !!of_match_device(drv->of_match_table, dev); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * dp_aux_ep_probe() - The probe function for the dp_aux_bus. | ||||
|  * @dev: The device to probe. | ||||
|  * | ||||
|  * Calls through to the endpoint driver probe. | ||||
|  * | ||||
|  * Return: 0 if no error or negative error code. | ||||
|  */ | ||||
| static int dp_aux_ep_probe(struct device *dev) | ||||
| { | ||||
| 	struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); | ||||
| 	struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = dev_pm_domain_attach(dev, true); | ||||
| 	if (ret) | ||||
| 		return dev_err_probe(dev, ret, "Failed to attach to PM Domain\n"); | ||||
| 
 | ||||
| 	ret = aux_ep_drv->probe(aux_ep); | ||||
| 	if (ret) | ||||
| 		dev_pm_domain_detach(dev, true); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * dp_aux_ep_remove() - The remove function for the dp_aux_bus. | ||||
|  * @dev: The device to remove. | ||||
|  * | ||||
|  * Calls through to the endpoint driver remove. | ||||
|  * | ||||
|  * Return: 0 if no error or negative error code. | ||||
|  */ | ||||
| static int dp_aux_ep_remove(struct device *dev) | ||||
| { | ||||
| 	struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); | ||||
| 	struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); | ||||
| 
 | ||||
| 	if (aux_ep_drv->remove) | ||||
| 		aux_ep_drv->remove(aux_ep); | ||||
| 	dev_pm_domain_detach(dev, true); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * dp_aux_ep_shutdown() - The shutdown function for the dp_aux_bus. | ||||
|  * @dev: The device to shutdown. | ||||
|  * | ||||
|  * Calls through to the endpoint driver shutdown. | ||||
|  */ | ||||
| static void dp_aux_ep_shutdown(struct device *dev) | ||||
| { | ||||
| 	struct dp_aux_ep_driver *aux_ep_drv; | ||||
| 
 | ||||
| 	if (!dev->driver) | ||||
| 		return; | ||||
| 
 | ||||
| 	aux_ep_drv = to_dp_aux_ep_drv(dev->driver); | ||||
| 	if (aux_ep_drv->shutdown) | ||||
| 		aux_ep_drv->shutdown(to_dp_aux_ep_dev(dev)); | ||||
| } | ||||
| 
 | ||||
| static struct bus_type dp_aux_bus_type = { | ||||
| 	.name		= "dp-aux", | ||||
| 	.match		= dp_aux_ep_match, | ||||
| 	.probe		= dp_aux_ep_probe, | ||||
| 	.remove		= dp_aux_ep_remove, | ||||
| 	.shutdown	= dp_aux_ep_shutdown, | ||||
| }; | ||||
| 
 | ||||
| static ssize_t modalias_show(struct device *dev, | ||||
| 			     struct device_attribute *attr, char *buf) | ||||
| { | ||||
| 	return of_device_modalias(dev, buf, PAGE_SIZE); | ||||
| } | ||||
| static DEVICE_ATTR_RO(modalias); | ||||
| 
 | ||||
| static struct attribute *dp_aux_ep_dev_attrs[] = { | ||||
| 	&dev_attr_modalias.attr, | ||||
| 	NULL, | ||||
| }; | ||||
| ATTRIBUTE_GROUPS(dp_aux_ep_dev); | ||||
| 
 | ||||
| /**
 | ||||
|  * dp_aux_ep_dev_release() - Free memory for the dp_aux_ep device | ||||
|  * @dev: The device to free. | ||||
|  * | ||||
|  * Return: 0 if no error or negative error code. | ||||
|  */ | ||||
| static void dp_aux_ep_dev_release(struct device *dev) | ||||
| { | ||||
| 	kfree(to_dp_aux_ep_dev(dev)); | ||||
| } | ||||
| 
 | ||||
| static struct device_type dp_aux_device_type_type = { | ||||
| 	.groups		= dp_aux_ep_dev_groups, | ||||
| 	.uevent		= of_device_uevent_modalias, | ||||
| 	.release	= dp_aux_ep_dev_release, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * of_dp_aux_ep_destroy() - Destroy an DP AUX endpoint device | ||||
|  * @dev: The device to destroy. | ||||
|  * @data: Not used | ||||
|  * | ||||
|  * This is just used as a callback by of_dp_aux_depopulate_ep_devices() and | ||||
|  * is called for _all_ of the child devices of the device providing the AUX bus. | ||||
|  * We'll only act on those that are of type "dp_aux_bus_type". | ||||
|  * | ||||
|  * This function is effectively an inverse of what's in the loop | ||||
|  * in of_dp_aux_populate_ep_devices(). | ||||
|  * | ||||
|  * Return: 0 if no error or negative error code. | ||||
|  */ | ||||
| static int of_dp_aux_ep_destroy(struct device *dev, void *data) | ||||
| { | ||||
| 	struct device_node *np = dev->of_node; | ||||
| 
 | ||||
| 	if (dev->bus != &dp_aux_bus_type) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (!of_node_check_flag(np, OF_POPULATED)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	of_node_clear_flag(np, OF_POPULATED); | ||||
| 	of_node_put(np); | ||||
| 
 | ||||
| 	device_unregister(dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * of_dp_aux_depopulate_ep_devices() - Undo of_dp_aux_populate_ep_devices | ||||
|  * @aux: The AUX channel whose devices we want to depopulate | ||||
|  * | ||||
|  * This will destroy all devices that were created | ||||
|  * by of_dp_aux_populate_ep_devices(). | ||||
|  */ | ||||
| void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux) | ||||
| { | ||||
| 	device_for_each_child_reverse(aux->dev, NULL, of_dp_aux_ep_destroy); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(of_dp_aux_depopulate_ep_devices); | ||||
| 
 | ||||
| /**
 | ||||
|  * of_dp_aux_populate_ep_devices() - Populate the endpoint devices on the DP AUX | ||||
|  * @aux: The AUX channel whose devices we want to populate. It is required that | ||||
|  *       drm_dp_aux_init() has already been called for this AUX channel. | ||||
|  * | ||||
|  * This will populate all the devices under the "aux-bus" node of the device | ||||
|  * providing the AUX channel (AKA aux->dev). | ||||
|  * | ||||
|  * When this function finishes, it is _possible_ (but not guaranteed) that | ||||
|  * our sub-devices will have finished probing. It should be noted that if our | ||||
|  * sub-devices return -EPROBE_DEFER that we will not return any error codes | ||||
|  * ourselves but our sub-devices will _not_ have actually probed successfully | ||||
|  * yet. There may be other cases (maybe added in the future?) where sub-devices | ||||
|  * won't have been probed yet when this function returns, so it's best not to | ||||
|  * rely on that. | ||||
|  * | ||||
|  * If this function succeeds you should later make sure you call | ||||
|  * of_dp_aux_depopulate_ep_devices() to undo it, or just use the devm version | ||||
|  * of this function. | ||||
|  * | ||||
|  * Return: 0 if no error or negative error code. | ||||
|  */ | ||||
| int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) | ||||
| { | ||||
| 	struct device_node *bus, *np; | ||||
| 	struct dp_aux_ep_device *aux_ep; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* drm_dp_aux_init() should have been called already; warn if not */ | ||||
| 	WARN_ON_ONCE(!aux->ddc.algo); | ||||
| 
 | ||||
| 	if (!aux->dev->of_node) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	bus = of_get_child_by_name(aux->dev->of_node, "aux-bus"); | ||||
| 	if (!bus) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	for_each_available_child_of_node(bus, np) { | ||||
| 		if (of_node_test_and_set_flag(np, OF_POPULATED)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		aux_ep = kzalloc(sizeof(*aux_ep), GFP_KERNEL); | ||||
| 		if (!aux_ep) | ||||
| 			continue; | ||||
| 		aux_ep->aux = aux; | ||||
| 
 | ||||
| 		aux_ep->dev.parent = aux->dev; | ||||
| 		aux_ep->dev.bus = &dp_aux_bus_type; | ||||
| 		aux_ep->dev.type = &dp_aux_device_type_type; | ||||
| 		aux_ep->dev.of_node = of_node_get(np); | ||||
| 		dev_set_name(&aux_ep->dev, "aux-%s", dev_name(aux->dev)); | ||||
| 
 | ||||
| 		ret = device_register(&aux_ep->dev); | ||||
| 		if (ret) { | ||||
| 			dev_err(aux->dev, "Failed to create AUX EP for %pOF: %d\n", np, ret); | ||||
| 			of_node_clear_flag(np, OF_POPULATED); | ||||
| 			of_node_put(np); | ||||
| 
 | ||||
| 			/*
 | ||||
| 			 * As per docs of device_register(), call this instead | ||||
| 			 * of kfree() directly for error cases. | ||||
| 			 */ | ||||
| 			put_device(&aux_ep->dev); | ||||
| 
 | ||||
| 			/*
 | ||||
| 			 * Following in the footsteps of of_i2c_register_devices(), | ||||
| 			 * we won't fail the whole function here--we'll just | ||||
| 			 * continue registering any other devices we find. | ||||
| 			 */ | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	of_node_put(bus); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void of_dp_aux_depopulate_ep_devices_void(void *data) | ||||
| { | ||||
| 	of_dp_aux_depopulate_ep_devices(data); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * devm_of_dp_aux_populate_ep_devices() - devm wrapper for of_dp_aux_populate_ep_devices() | ||||
|  * @aux: The AUX channel whose devices we want to populate | ||||
|  * | ||||
|  * Handles freeing w/ devm on the device "aux->dev". | ||||
|  * | ||||
|  * Return: 0 if no error or negative error code. | ||||
|  */ | ||||
| int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = of_dp_aux_populate_ep_devices(aux); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return devm_add_action_or_reset(aux->dev, | ||||
| 					of_dp_aux_depopulate_ep_devices_void, | ||||
| 					aux); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_ep_devices); | ||||
| 
 | ||||
| int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner) | ||||
| { | ||||
| 	drv->driver.owner = owner; | ||||
| 	drv->driver.bus = &dp_aux_bus_type; | ||||
| 
 | ||||
| 	return driver_register(&drv->driver); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register); | ||||
| 
 | ||||
| void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *drv) | ||||
| { | ||||
| 	driver_unregister(&drv->driver); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(dp_aux_dp_driver_unregister); | ||||
| 
 | ||||
| static int __init dp_aux_bus_init(void) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = bus_register(&dp_aux_bus_type); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void __exit dp_aux_bus_exit(void) | ||||
| { | ||||
| 	bus_unregister(&dp_aux_bus_type); | ||||
| } | ||||
| 
 | ||||
| subsys_initcall(dp_aux_bus_init); | ||||
| module_exit(dp_aux_bus_exit); | ||||
| 
 | ||||
| MODULE_AUTHOR("Douglas Anderson <dianders@chromium.org>"); | ||||
| MODULE_DESCRIPTION("DRM DisplayPort AUX bus"); | ||||
| MODULE_LICENSE("GPL v2"); | ||||
| @ -33,9 +33,17 @@ | ||||
| #include <drm/drm_print.h> | ||||
| #include <drm/drm_vblank.h> | ||||
| #include <drm/drm_dp_mst_helper.h> | ||||
| #include <drm/drm_panel.h> | ||||
| 
 | ||||
| #include "drm_crtc_helper_internal.h" | ||||
| 
 | ||||
| struct dp_aux_backlight { | ||||
| 	struct backlight_device *base; | ||||
| 	struct drm_dp_aux *aux; | ||||
| 	struct drm_edp_backlight_info info; | ||||
| 	bool enabled; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * DOC: dp helpers | ||||
|  * | ||||
| @ -3115,3 +3123,457 @@ int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc) | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_dp_pcon_convert_rgb_to_ycbcr); | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_edp_backlight_set_level() - Set the backlight level of an eDP panel via AUX | ||||
|  * @aux: The DP AUX channel to use | ||||
|  * @bl: Backlight capability info from drm_edp_backlight_init() | ||||
|  * @level: The brightness level to set | ||||
|  * | ||||
|  * Sets the brightness level of an eDP panel's backlight. Note that the panel's backlight must | ||||
|  * already have been enabled by the driver by calling drm_edp_backlight_enable(). | ||||
|  * | ||||
|  * Returns: %0 on success, negative error code on failure | ||||
|  */ | ||||
| int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, | ||||
| 				u16 level) | ||||
| { | ||||
| 	int ret; | ||||
| 	u8 buf[2] = { 0 }; | ||||
| 
 | ||||
| 	if (bl->lsb_reg_used) { | ||||
| 		buf[0] = (level & 0xff00) >> 8; | ||||
| 		buf[1] = (level & 0x00ff); | ||||
| 	} else { | ||||
| 		buf[0] = level; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_write(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, sizeof(buf)); | ||||
| 	if (ret != sizeof(buf)) { | ||||
| 		drm_err(aux->drm_dev, | ||||
| 			"%s: Failed to write aux backlight level: %d\n", | ||||
| 			aux->name, ret); | ||||
| 		return ret < 0 ? ret : -EIO; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_edp_backlight_set_level); | ||||
| 
 | ||||
| static int | ||||
| drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, | ||||
| 			     bool enable) | ||||
| { | ||||
| 	int ret; | ||||
| 	u8 buf; | ||||
| 
 | ||||
| 	/* The panel uses something other then DPCD for enabling its backlight */ | ||||
| 	if (!bl->aux_enable) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_readb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, &buf); | ||||
| 	if (ret != 1) { | ||||
| 		drm_err(aux->drm_dev, "%s: Failed to read eDP display control register: %d\n", | ||||
| 			aux->name, ret); | ||||
| 		return ret < 0 ? ret : -EIO; | ||||
| 	} | ||||
| 	if (enable) | ||||
| 		buf |= DP_EDP_BACKLIGHT_ENABLE; | ||||
| 	else | ||||
| 		buf &= ~DP_EDP_BACKLIGHT_ENABLE; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_writeb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, buf); | ||||
| 	if (ret != 1) { | ||||
| 		drm_err(aux->drm_dev, "%s: Failed to write eDP display control register: %d\n", | ||||
| 			aux->name, ret); | ||||
| 		return ret < 0 ? ret : -EIO; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_edp_backlight_enable() - Enable an eDP panel's backlight using DPCD | ||||
|  * @aux: The DP AUX channel to use | ||||
|  * @bl: Backlight capability info from drm_edp_backlight_init() | ||||
|  * @level: The initial backlight level to set via AUX, if there is one | ||||
|  * | ||||
|  * This function handles enabling DPCD backlight controls on a panel over DPCD, while additionally | ||||
|  * restoring any important backlight state such as the given backlight level, the brightness byte | ||||
|  * count, backlight frequency, etc. | ||||
|  * | ||||
|  * Note that certain panels, while supporting brightness level controls over DPCD, may not support | ||||
|  * having their backlights enabled via the standard %DP_EDP_DISPLAY_CONTROL_REGISTER. On such panels | ||||
|  * &drm_edp_backlight_info.aux_enable will be set to %false, this function will skip the step of | ||||
|  * programming the %DP_EDP_DISPLAY_CONTROL_REGISTER, and the driver must perform the required | ||||
|  * implementation specific step for enabling the backlight after calling this function. | ||||
|  * | ||||
|  * Returns: %0 on success, negative error code on failure. | ||||
|  */ | ||||
| int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, | ||||
| 			     const u16 level) | ||||
| { | ||||
| 	int ret; | ||||
| 	u8 dpcd_buf, new_dpcd_buf; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf); | ||||
| 	if (ret != 1) { | ||||
| 		drm_dbg_kms(aux->drm_dev, | ||||
| 			    "%s: Failed to read backlight mode: %d\n", aux->name, ret); | ||||
| 		return ret < 0 ? ret : -EIO; | ||||
| 	} | ||||
| 
 | ||||
| 	new_dpcd_buf = dpcd_buf; | ||||
| 
 | ||||
| 	if ((dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) != DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { | ||||
| 		new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; | ||||
| 		new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; | ||||
| 
 | ||||
| 		ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count); | ||||
| 		if (ret != 1) | ||||
| 			drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", | ||||
| 				    aux->name, ret); | ||||
| 	} | ||||
| 
 | ||||
| 	if (bl->pwm_freq_pre_divider) { | ||||
| 		ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_FREQ_SET, bl->pwm_freq_pre_divider); | ||||
| 		if (ret != 1) | ||||
| 			drm_dbg_kms(aux->drm_dev, | ||||
| 				    "%s: Failed to write aux backlight frequency: %d\n", | ||||
| 				    aux->name, ret); | ||||
| 		else | ||||
| 			new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; | ||||
| 	} | ||||
| 
 | ||||
| 	if (new_dpcd_buf != dpcd_buf) { | ||||
| 		ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf); | ||||
| 		if (ret != 1) { | ||||
| 			drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n", | ||||
| 				    aux->name, ret); | ||||
| 			return ret < 0 ? ret : -EIO; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_edp_backlight_set_level(aux, bl, level); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 	ret = drm_edp_backlight_set_enable(aux, bl, true); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_edp_backlight_enable); | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_edp_backlight_disable() - Disable an eDP backlight using DPCD, if supported | ||||
|  * @aux: The DP AUX channel to use | ||||
|  * @bl: Backlight capability info from drm_edp_backlight_init() | ||||
|  * | ||||
|  * This function handles disabling DPCD backlight controls on a panel over AUX. Note that some | ||||
|  * panels have backlights that are enabled/disabled by other means, despite having their brightness | ||||
|  * values controlled through DPCD. On such panels &drm_edp_backlight_info.aux_enable will be set to | ||||
|  * %false, this function will become a no-op (and we will skip updating | ||||
|  * %DP_EDP_DISPLAY_CONTROL_REGISTER), and the driver must take care to perform it's own | ||||
|  * implementation specific step for disabling the backlight. | ||||
|  * | ||||
|  * Returns: %0 on success or no-op, negative error code on failure. | ||||
|  */ | ||||
| int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = drm_edp_backlight_set_enable(aux, bl, false); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_edp_backlight_disable); | ||||
| 
 | ||||
| static inline int | ||||
| drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, | ||||
| 			    u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]) | ||||
| { | ||||
| 	int fxp, fxp_min, fxp_max, fxp_actual, f = 1; | ||||
| 	int ret; | ||||
| 	u8 pn, pn_min, pn_max; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); | ||||
| 	if (ret != 1) { | ||||
| 		drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", | ||||
| 			    aux->name, ret); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; | ||||
| 	bl->max = (1 << pn) - 1; | ||||
| 	if (!driver_pwm_freq_hz) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Set PWM Frequency divider to match desired frequency provided by the driver. | ||||
| 	 * The PWM Frequency is calculated as 27Mhz / (F x P). | ||||
| 	 * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the | ||||
| 	 *             EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) | ||||
| 	 * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the | ||||
| 	 *             EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) | ||||
| 	 */ | ||||
| 
 | ||||
| 	/* Find desired value of (F x P)
 | ||||
| 	 * Note that, if F x P is out of supported range, the maximum value or minimum value will | ||||
| 	 * applied automatically. So no need to check that. | ||||
| 	 */ | ||||
| 	fxp = DIV_ROUND_CLOSEST(1000 * DP_EDP_BACKLIGHT_FREQ_BASE_KHZ, driver_pwm_freq_hz); | ||||
| 
 | ||||
| 	/* Use highest possible value of Pn for more granularity of brightness adjustment while
 | ||||
| 	 * satifying the conditions below. | ||||
| 	 * - Pn is in the range of Pn_min and Pn_max | ||||
| 	 * - F is in the range of 1 and 255 | ||||
| 	 * - FxP is within 25% of desired value. | ||||
| 	 *   Note: 25% is arbitrary value and may need some tweak. | ||||
| 	 */ | ||||
| 	ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); | ||||
| 	if (ret != 1) { | ||||
| 		drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", | ||||
| 			    aux->name, ret); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); | ||||
| 	if (ret != 1) { | ||||
| 		drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", | ||||
| 			    aux->name, ret); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; | ||||
| 	pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; | ||||
| 
 | ||||
| 	/* Ensure frequency is within 25% of desired value */ | ||||
| 	fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); | ||||
| 	fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); | ||||
| 	if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { | ||||
| 		drm_dbg_kms(aux->drm_dev, | ||||
| 			    "%s: Driver defined backlight frequency (%d) out of range\n", | ||||
| 			    aux->name, driver_pwm_freq_hz); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	for (pn = pn_max; pn >= pn_min; pn--) { | ||||
| 		f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); | ||||
| 		fxp_actual = f << pn; | ||||
| 		if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, pn); | ||||
| 	if (ret != 1) { | ||||
| 		drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", | ||||
| 			    aux->name, ret); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	bl->pwmgen_bit_count = pn; | ||||
| 	bl->max = (1 << pn) - 1; | ||||
| 
 | ||||
| 	if (edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) { | ||||
| 		bl->pwm_freq_pre_divider = f; | ||||
| 		drm_dbg_kms(aux->drm_dev, "%s: Using backlight frequency from driver (%dHz)\n", | ||||
| 			    aux->name, driver_pwm_freq_hz); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline int | ||||
| drm_edp_backlight_probe_level(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, | ||||
| 			      u8 *current_mode) | ||||
| { | ||||
| 	int ret; | ||||
| 	u8 buf[2]; | ||||
| 	u8 mode_reg; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &mode_reg); | ||||
| 	if (ret != 1) { | ||||
| 		drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight mode: %d\n", | ||||
| 			    aux->name, ret); | ||||
| 		return ret < 0 ? ret : -EIO; | ||||
| 	} | ||||
| 
 | ||||
| 	*current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK); | ||||
| 	if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { | ||||
| 		int size = 1 + bl->lsb_reg_used; | ||||
| 
 | ||||
| 		ret = drm_dp_dpcd_read(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, size); | ||||
| 		if (ret != size) { | ||||
| 			drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight level: %d\n", | ||||
| 				    aux->name, ret); | ||||
| 			return ret < 0 ? ret : -EIO; | ||||
| 		} | ||||
| 
 | ||||
| 		if (bl->lsb_reg_used) | ||||
| 			return (buf[0] << 8) | buf[1]; | ||||
| 		else | ||||
| 			return buf[0]; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If we're not in DPCD control mode yet, the programmed brightness value is meaningless and | ||||
| 	 * the driver should assume max brightness | ||||
| 	 */ | ||||
| 	return bl->max; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_edp_backlight_init() - Probe a display panel's TCON using the standard VESA eDP backlight | ||||
|  * interface. | ||||
|  * @aux: The DP aux device to use for probing | ||||
|  * @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight | ||||
|  * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz | ||||
|  * @edp_dpcd: A cached copy of the eDP DPCD | ||||
|  * @current_level: Where to store the probed brightness level | ||||
|  * @current_mode: Where to store the currently set backlight control mode | ||||
|  * | ||||
|  * Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities, | ||||
|  * along with also probing the current and maximum supported brightness levels. | ||||
|  * | ||||
|  * If @driver_pwm_freq_hz is non-zero, this will be used as the backlight frequency. Otherwise, the | ||||
|  * default frequency from the panel is used. | ||||
|  * | ||||
|  * Returns: %0 on success, negative error code on failure. | ||||
|  */ | ||||
| int | ||||
| drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, | ||||
| 		       u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE], | ||||
| 		       u16 *current_level, u8 *current_mode) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) | ||||
| 		bl->aux_enable = true; | ||||
| 	if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) | ||||
| 		bl->lsb_reg_used = true; | ||||
| 
 | ||||
| 	ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = drm_edp_backlight_probe_level(aux, bl, current_mode); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 	*current_level = ret; | ||||
| 
 | ||||
| 	drm_dbg_kms(aux->drm_dev, | ||||
| 		    "%s: Found backlight level=%d/%d pwm_freq_pre_divider=%d mode=%x\n", | ||||
| 		    aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, *current_mode); | ||||
| 	drm_dbg_kms(aux->drm_dev, | ||||
| 		    "%s: Backlight caps: pwmgen_bit_count=%d lsb_reg_used=%d aux_enable=%d\n", | ||||
| 		    aux->name, bl->pwmgen_bit_count, bl->lsb_reg_used, bl->aux_enable); | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_edp_backlight_init); | ||||
| 
 | ||||
| #if IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \ | ||||
| 	(IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)) | ||||
| 
 | ||||
| static int dp_aux_backlight_update_status(struct backlight_device *bd) | ||||
| { | ||||
| 	struct dp_aux_backlight *bl = bl_get_data(bd); | ||||
| 	u16 brightness = backlight_get_brightness(bd); | ||||
| 	int ret = 0; | ||||
| 
 | ||||
| 	if (!backlight_is_blank(bd)) { | ||||
| 		if (!bl->enabled) { | ||||
| 			drm_edp_backlight_enable(bl->aux, &bl->info, brightness); | ||||
| 			bl->enabled = true; | ||||
| 			return 0; | ||||
| 		} | ||||
| 		ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness); | ||||
| 	} else { | ||||
| 		if (bl->enabled) { | ||||
| 			drm_edp_backlight_disable(bl->aux, &bl->info); | ||||
| 			bl->enabled = false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static const struct backlight_ops dp_aux_bl_ops = { | ||||
| 	.update_status = dp_aux_backlight_update_status, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_panel_dp_aux_backlight - create and use DP AUX backlight | ||||
|  * @panel: DRM panel | ||||
|  * @aux: The DP AUX channel to use | ||||
|  * | ||||
|  * Use this function to create and handle backlight if your panel | ||||
|  * supports backlight control over DP AUX channel using DPCD | ||||
|  * registers as per VESA's standard backlight control interface. | ||||
|  * | ||||
|  * When the panel is enabled backlight will be enabled after a | ||||
|  * successful call to &drm_panel_funcs.enable() | ||||
|  * | ||||
|  * When the panel is disabled backlight will be disabled before the | ||||
|  * call to &drm_panel_funcs.disable(). | ||||
|  * | ||||
|  * A typical implementation for a panel driver supporting backlight | ||||
|  * control over DP AUX will call this function at probe time. | ||||
|  * Backlight will then be handled transparently without requiring | ||||
|  * any intervention from the driver. | ||||
|  * | ||||
|  * drm_panel_dp_aux_backlight() must be called after the call to drm_panel_init(). | ||||
|  * | ||||
|  * Return: 0 on success or a negative error code on failure. | ||||
|  */ | ||||
| int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux) | ||||
| { | ||||
| 	struct dp_aux_backlight *bl; | ||||
| 	struct backlight_properties props = { 0 }; | ||||
| 	u16 current_level; | ||||
| 	u8 current_mode; | ||||
| 	u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (!panel || !panel->dev || !aux) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_read(aux, DP_EDP_DPCD_REV, edp_dpcd, | ||||
| 			       EDP_DISPLAY_CTL_CAP_SIZE); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	if (!drm_edp_backlight_supported(edp_dpcd)) { | ||||
| 		DRM_DEV_INFO(panel->dev, "DP AUX backlight is not supported\n"); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	bl = devm_kzalloc(panel->dev, sizeof(*bl), GFP_KERNEL); | ||||
| 	if (!bl) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	bl->aux = aux; | ||||
| 
 | ||||
| 	ret = drm_edp_backlight_init(aux, &bl->info, 0, edp_dpcd, | ||||
| 				     ¤t_level, ¤t_mode); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	props.type = BACKLIGHT_RAW; | ||||
| 	props.brightness = current_level; | ||||
| 	props.max_brightness = bl->info.max; | ||||
| 
 | ||||
| 	bl->base = devm_backlight_device_register(panel->dev, "dp_aux_backlight", | ||||
| 						  panel->dev, bl, | ||||
| 						  &dp_aux_bl_ops, &props); | ||||
| 	if (IS_ERR(bl->base)) | ||||
| 		return PTR_ERR(bl->base); | ||||
| 
 | ||||
| 	backlight_disable(bl->base); | ||||
| 
 | ||||
| 	panel->backlight = bl->base; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_panel_dp_aux_backlight); | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
| @ -1148,15 +1148,6 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) | ||||
| 		return -EACCES; | ||||
| 	} | ||||
| 
 | ||||
| 	if (node->readonly) { | ||||
| 		if (vma->vm_flags & VM_WRITE) { | ||||
| 			drm_gem_object_put(obj); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 
 | ||||
| 		vma->vm_flags &= ~VM_MAYWRITE; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_gem_mmap_obj(obj, drm_vma_node_size(node) << PAGE_SHIFT, | ||||
| 			       vma); | ||||
| 
 | ||||
| @ -1311,6 +1302,9 @@ EXPORT_SYMBOL(drm_gem_unlock_reservations); | ||||
|  * @fence_array: array of dma_fence * for the job to block on. | ||||
|  * @fence: the dma_fence to add to the list of dependencies. | ||||
|  * | ||||
|  * This functions consumes the reference for @fence both on success and error | ||||
|  * cases. | ||||
|  * | ||||
|  * Returns: | ||||
|  * 0 on success, or an error on failing to expand the array. | ||||
|  */ | ||||
|  | ||||
| @ -135,6 +135,9 @@ | ||||
|  * GEM based framebuffer drivers which have their buffers always pinned in | ||||
|  * memory. | ||||
|  * | ||||
|  * This function is the default implementation for GEM drivers of | ||||
|  * &drm_plane_helper_funcs.prepare_fb if no callback is provided. | ||||
|  * | ||||
|  * See drm_atomic_set_fence_for_plane() for a discussion of implicit and | ||||
|  * explicit fencing in atomic modeset updates. | ||||
|  */ | ||||
| @ -179,6 +182,27 @@ EXPORT_SYMBOL(drm_gem_simple_display_pipe_prepare_fb); | ||||
|  * Shadow-buffered Planes | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * __drm_gem_duplicate_shadow_plane_state - duplicates shadow-buffered plane state | ||||
|  * @plane: the plane | ||||
|  * @new_shadow_plane_state: the new shadow-buffered plane state | ||||
|  * | ||||
|  * This function duplicates shadow-buffered plane state. This is helpful for drivers | ||||
|  * that subclass struct drm_shadow_plane_state. | ||||
|  * | ||||
|  * The function does not duplicate existing mappings of the shadow buffers. | ||||
|  * Mappings are maintained during the atomic commit by the plane's prepare_fb | ||||
|  * and cleanup_fb helpers. See drm_gem_prepare_shadow_fb() and drm_gem_cleanup_shadow_fb() | ||||
|  * for corresponding helpers. | ||||
|  */ | ||||
| void | ||||
| __drm_gem_duplicate_shadow_plane_state(struct drm_plane *plane, | ||||
| 				       struct drm_shadow_plane_state *new_shadow_plane_state) | ||||
| { | ||||
| 	__drm_atomic_helper_plane_duplicate_state(plane, &new_shadow_plane_state->base); | ||||
| } | ||||
| EXPORT_SYMBOL(__drm_gem_duplicate_shadow_plane_state); | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_gem_duplicate_shadow_plane_state - duplicates shadow-buffered plane state | ||||
|  * @plane: the plane | ||||
| @ -208,12 +232,25 @@ drm_gem_duplicate_shadow_plane_state(struct drm_plane *plane) | ||||
| 	new_shadow_plane_state = kzalloc(sizeof(*new_shadow_plane_state), GFP_KERNEL); | ||||
| 	if (!new_shadow_plane_state) | ||||
| 		return NULL; | ||||
| 	__drm_atomic_helper_plane_duplicate_state(plane, &new_shadow_plane_state->base); | ||||
| 	__drm_gem_duplicate_shadow_plane_state(plane, new_shadow_plane_state); | ||||
| 
 | ||||
| 	return &new_shadow_plane_state->base; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_gem_duplicate_shadow_plane_state); | ||||
| 
 | ||||
| /**
 | ||||
|  * __drm_gem_destroy_shadow_plane_state - cleans up shadow-buffered plane state | ||||
|  * @shadow_plane_state: the shadow-buffered plane state | ||||
|  * | ||||
|  * This function cleans up shadow-buffered plane state. Helpful for drivers that | ||||
|  * subclass struct drm_shadow_plane_state. | ||||
|  */ | ||||
| void __drm_gem_destroy_shadow_plane_state(struct drm_shadow_plane_state *shadow_plane_state) | ||||
| { | ||||
| 	__drm_atomic_helper_plane_destroy_state(&shadow_plane_state->base); | ||||
| } | ||||
| EXPORT_SYMBOL(__drm_gem_destroy_shadow_plane_state); | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_gem_destroy_shadow_plane_state - deletes shadow-buffered plane state | ||||
|  * @plane: the plane | ||||
| @ -229,11 +266,26 @@ void drm_gem_destroy_shadow_plane_state(struct drm_plane *plane, | ||||
| 	struct drm_shadow_plane_state *shadow_plane_state = | ||||
| 		to_drm_shadow_plane_state(plane_state); | ||||
| 
 | ||||
| 	__drm_atomic_helper_plane_destroy_state(&shadow_plane_state->base); | ||||
| 	__drm_gem_destroy_shadow_plane_state(shadow_plane_state); | ||||
| 	kfree(shadow_plane_state); | ||||
| } | ||||
| EXPORT_SYMBOL(drm_gem_destroy_shadow_plane_state); | ||||
| 
 | ||||
| /**
 | ||||
|  * __drm_gem_reset_shadow_plane - resets a shadow-buffered plane | ||||
|  * @plane: the plane | ||||
|  * @shadow_plane_state: the shadow-buffered plane state | ||||
|  * | ||||
|  * This function resets state for shadow-buffered planes. Helpful | ||||
|  * for drivers that subclass struct drm_shadow_plane_state. | ||||
|  */ | ||||
| void __drm_gem_reset_shadow_plane(struct drm_plane *plane, | ||||
| 				  struct drm_shadow_plane_state *shadow_plane_state) | ||||
| { | ||||
| 	__drm_atomic_helper_plane_reset(plane, &shadow_plane_state->base); | ||||
| } | ||||
| EXPORT_SYMBOL(__drm_gem_reset_shadow_plane); | ||||
| 
 | ||||
| /**
 | ||||
|  * drm_gem_reset_shadow_plane - resets a shadow-buffered plane | ||||
|  * @plane: the plane | ||||
| @ -255,7 +307,7 @@ void drm_gem_reset_shadow_plane(struct drm_plane *plane) | ||||
| 	shadow_plane_state = kzalloc(sizeof(*shadow_plane_state), GFP_KERNEL); | ||||
| 	if (!shadow_plane_state) | ||||
| 		return; | ||||
| 	__drm_atomic_helper_plane_reset(plane, &shadow_plane_state->base); | ||||
| 	__drm_gem_reset_shadow_plane(plane, shadow_plane_state); | ||||
| } | ||||
| EXPORT_SYMBOL(drm_gem_reset_shadow_plane); | ||||
| 
 | ||||
|  | ||||
| @ -505,13 +505,13 @@ int drm_gem_shmem_dumb_create(struct drm_file *file, struct drm_device *dev, | ||||
| 
 | ||||
| 	if (!args->pitch || !args->size) { | ||||
| 		args->pitch = min_pitch; | ||||
| 		args->size = args->pitch * args->height; | ||||
| 		args->size = PAGE_ALIGN(args->pitch * args->height); | ||||
| 	} else { | ||||
| 		/* ensure sane minimum values */ | ||||
| 		if (args->pitch < min_pitch) | ||||
| 			args->pitch = min_pitch; | ||||
| 		if (args->size < args->pitch * args->height) | ||||
| 			args->size = args->pitch * args->height; | ||||
| 			args->size = PAGE_ALIGN(args->pitch * args->height); | ||||
| 	} | ||||
| 
 | ||||
| 	shmem = drm_gem_shmem_create_with_handle(file, dev, args->size, &args->handle); | ||||
|  | ||||
| @ -1012,9 +1012,8 @@ static void drm_vram_mm_cleanup(struct drm_vram_mm *vmm) | ||||
|  * Helpers for integration with struct drm_device | ||||
|  */ | ||||
| 
 | ||||
| /* deprecated; use drmm_vram_mm_init() */ | ||||
| struct drm_vram_mm *drm_vram_helper_alloc_mm( | ||||
| 	struct drm_device *dev, uint64_t vram_base, size_t vram_size) | ||||
| static struct drm_vram_mm *drm_vram_helper_alloc_mm(struct drm_device *dev, uint64_t vram_base, | ||||
| 						    size_t vram_size) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| @ -1036,9 +1035,8 @@ err_kfree: | ||||
| 	dev->vram_mm = NULL; | ||||
| 	return ERR_PTR(ret); | ||||
| } | ||||
| EXPORT_SYMBOL(drm_vram_helper_alloc_mm); | ||||
| 
 | ||||
| void drm_vram_helper_release_mm(struct drm_device *dev) | ||||
| static void drm_vram_helper_release_mm(struct drm_device *dev) | ||||
| { | ||||
| 	if (!dev->vram_mm) | ||||
| 		return; | ||||
| @ -1047,7 +1045,6 @@ void drm_vram_helper_release_mm(struct drm_device *dev) | ||||
| 	kfree(dev->vram_mm); | ||||
| 	dev->vram_mm = NULL; | ||||
| } | ||||
| EXPORT_SYMBOL(drm_vram_helper_release_mm); | ||||
| 
 | ||||
| static void drm_vram_mm_release(struct drm_device *dev, void *ptr) | ||||
| { | ||||
|  | ||||
| @ -74,10 +74,8 @@ | ||||
|  * only supports devices with a single interrupt on the main device stored in | ||||
|  * &drm_device.dev and set as the device paramter in drm_dev_alloc(). | ||||
|  * | ||||
|  * These IRQ helpers are strictly optional. Drivers which roll their own only | ||||
|  * need to set &drm_device.irq_enabled to signal the DRM core that vblank | ||||
|  * interrupts are working. Since these helpers don't automatically clean up the | ||||
|  * requested interrupt like e.g. devm_request_irq() they're not really | ||||
|  * These IRQ helpers are strictly optional. Since these helpers don't automatically | ||||
|  * clean up the requested interrupt like e.g. devm_request_irq() they're not really | ||||
|  * recommended. | ||||
|  */ | ||||
| 
 | ||||
| @ -91,9 +89,7 @@ | ||||
|  * and after the installation. | ||||
|  * | ||||
|  * This is the simplified helper interface provided for drivers with no special | ||||
|  * needs. Drivers which need to install interrupt handlers for multiple | ||||
|  * interrupts must instead set &drm_device.irq_enabled to signal the DRM core | ||||
|  * that vblank interrupts are available. | ||||
|  * needs. | ||||
|  * | ||||
|  * @irq must match the interrupt number that would be passed to request_irq(), | ||||
|  * if called directly instead of using this helper function. | ||||
| @ -156,8 +152,7 @@ EXPORT_SYMBOL(drm_irq_install); | ||||
|  * | ||||
|  * Calls the driver's &drm_driver.irq_uninstall function and unregisters the IRQ | ||||
|  * handler.  This should only be called by drivers which used drm_irq_install() | ||||
|  * to set up their interrupt handler. Other drivers must only reset | ||||
|  * &drm_device.irq_enabled to false. | ||||
|  * to set up their interrupt handler. | ||||
|  * | ||||
|  * Note that for kernel modesetting drivers it is a bug if this function fails. | ||||
|  * The sanity checks are only to catch buggy user modesetting drivers which call | ||||
|  | ||||
| @ -928,6 +928,59 @@ static int mipi_dbi_spi1_transfer(struct mipi_dbi *dbi, int dc, | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int mipi_dbi_typec1_command_read(struct mipi_dbi *dbi, u8 *cmd, | ||||
| 					u8 *data, size_t len) | ||||
| { | ||||
| 	struct spi_device *spi = dbi->spi; | ||||
| 	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED, | ||||
| 			     spi->max_speed_hz / 2); | ||||
| 	struct spi_transfer tr[2] = { | ||||
| 		{ | ||||
| 			.speed_hz = speed_hz, | ||||
| 			.bits_per_word = 9, | ||||
| 			.tx_buf = dbi->tx_buf9, | ||||
| 			.len = 2, | ||||
| 		}, { | ||||
| 			.speed_hz = speed_hz, | ||||
| 			.bits_per_word = 8, | ||||
| 			.len = len, | ||||
| 			.rx_buf = data, | ||||
| 		}, | ||||
| 	}; | ||||
| 	struct spi_message m; | ||||
| 	u16 *dst16; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (!len) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (!spi_is_bpw_supported(spi, 9)) { | ||||
| 		/*
 | ||||
| 		 * FIXME: implement something like mipi_dbi_spi1e_transfer() but | ||||
| 		 * for reads using emulation. | ||||
| 		 */ | ||||
| 		dev_err(&spi->dev, | ||||
| 			"reading on host not supporting 9 bpw not yet implemented\n"); | ||||
| 		return -EOPNOTSUPP; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Turn the 8bit command into a 16bit version of the command in the | ||||
| 	 * buffer. Only 9 bits of this will be used when executing the actual | ||||
| 	 * transfer. | ||||
| 	 */ | ||||
| 	dst16 = dbi->tx_buf9; | ||||
| 	dst16[0] = *cmd; | ||||
| 
 | ||||
| 	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr)); | ||||
| 	ret = spi_sync(spi, &m); | ||||
| 
 | ||||
| 	if (!ret) | ||||
| 		MIPI_DBI_DEBUG_COMMAND(*cmd, data, len); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int mipi_dbi_typec1_command(struct mipi_dbi *dbi, u8 *cmd, | ||||
| 				   u8 *parameters, size_t num) | ||||
| { | ||||
| @ -935,7 +988,7 @@ static int mipi_dbi_typec1_command(struct mipi_dbi *dbi, u8 *cmd, | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (mipi_dbi_command_is_read(dbi, *cmd)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 		return mipi_dbi_typec1_command_read(dbi, cmd, parameters, num); | ||||
| 
 | ||||
| 	MIPI_DBI_DEBUG_COMMAND(*cmd, parameters, num); | ||||
| 
 | ||||
|  | ||||
| @ -315,7 +315,7 @@ static int drm_of_lvds_get_remote_pixels_type( | ||||
| 
 | ||||
| 		remote_port = of_graph_get_remote_port(endpoint); | ||||
| 		if (!remote_port) { | ||||
| 			of_node_put(remote_port); | ||||
| 			of_node_put(endpoint); | ||||
| 			return -EPIPE; | ||||
| 		} | ||||
| 
 | ||||
| @ -331,8 +331,10 @@ static int drm_of_lvds_get_remote_pixels_type( | ||||
| 		 * configurations by passing the endpoints explicitly to | ||||
| 		 * drm_of_lvds_get_dual_link_pixel_order(). | ||||
| 		 */ | ||||
| 		if (!current_pt || pixels_type != current_pt) | ||||
| 		if (!current_pt || pixels_type != current_pt) { | ||||
| 			of_node_put(endpoint); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return pixels_type; | ||||
|  | ||||
| @ -9,6 +9,8 @@ | ||||
| #include <drm/drm_atomic.h> | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_bridge.h> | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_gem_atomic_helper.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_plane_helper.h> | ||||
| #include <drm/drm_probe_helper.h> | ||||
| @ -225,8 +227,14 @@ static int drm_simple_kms_plane_prepare_fb(struct drm_plane *plane, | ||||
| 	struct drm_simple_display_pipe *pipe; | ||||
| 
 | ||||
| 	pipe = container_of(plane, struct drm_simple_display_pipe, plane); | ||||
| 	if (!pipe->funcs || !pipe->funcs->prepare_fb) | ||||
| 		return 0; | ||||
| 	if (!pipe->funcs || !pipe->funcs->prepare_fb) { | ||||
| 		if (WARN_ON_ONCE(!drm_core_check_feature(plane->dev, DRIVER_GEM))) | ||||
| 			return 0; | ||||
| 
 | ||||
| 		WARN_ON_ONCE(pipe->funcs && pipe->funcs->cleanup_fb); | ||||
| 
 | ||||
| 		return drm_gem_simple_display_pipe_prepare_fb(pipe, state); | ||||
| 	} | ||||
| 
 | ||||
| 	return pipe->funcs->prepare_fb(pipe, state); | ||||
| } | ||||
|  | ||||
| @ -861,7 +861,7 @@ static int drm_syncobj_transfer_to_timeline(struct drm_file *file_private, | ||||
| 				     &fence); | ||||
| 	if (ret) | ||||
| 		goto err; | ||||
| 	chain = kzalloc(sizeof(struct dma_fence_chain), GFP_KERNEL); | ||||
| 	chain = dma_fence_chain_alloc(); | ||||
| 	if (!chain) { | ||||
| 		ret = -ENOMEM; | ||||
| 		goto err1; | ||||
| @ -1402,10 +1402,10 @@ drm_syncobj_timeline_signal_ioctl(struct drm_device *dev, void *data, | ||||
| 		goto err_points; | ||||
| 	} | ||||
| 	for (i = 0; i < args->count_handles; i++) { | ||||
| 		chains[i] = kzalloc(sizeof(struct dma_fence_chain), GFP_KERNEL); | ||||
| 		chains[i] = dma_fence_chain_alloc(); | ||||
| 		if (!chains[i]) { | ||||
| 			for (j = 0; j < i; j++) | ||||
| 				kfree(chains[j]); | ||||
| 				dma_fence_chain_free(chains[j]); | ||||
| 			ret = -ENOMEM; | ||||
| 			goto err_chains; | ||||
| 		} | ||||
|  | ||||
| @ -1737,6 +1737,15 @@ static void drm_wait_vblank_reply(struct drm_device *dev, unsigned int pipe, | ||||
| 	reply->tval_usec = ts.tv_nsec / 1000; | ||||
| } | ||||
| 
 | ||||
| static bool drm_wait_vblank_supported(struct drm_device *dev) | ||||
| { | ||||
| 	if  (IS_ENABLED(CONFIG_DRM_LEGACY)) { | ||||
| 		if (unlikely(drm_core_check_feature(dev, DRIVER_LEGACY))) | ||||
| 			return dev->irq_enabled; | ||||
| 	} | ||||
| 	return drm_dev_has_vblank(dev); | ||||
| } | ||||
| 
 | ||||
| int drm_wait_vblank_ioctl(struct drm_device *dev, void *data, | ||||
| 			  struct drm_file *file_priv) | ||||
| { | ||||
| @ -1748,7 +1757,7 @@ int drm_wait_vblank_ioctl(struct drm_device *dev, void *data, | ||||
| 	unsigned int pipe_index; | ||||
| 	unsigned int flags, pipe, high_pipe; | ||||
| 
 | ||||
| 	if (!dev->irq_enabled) | ||||
| 	if (!drm_wait_vblank_supported(dev)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 
 | ||||
| 	if (vblwait->request.type & _DRM_VBLANK_SIGNAL) | ||||
| @ -2023,7 +2032,7 @@ int drm_crtc_get_sequence_ioctl(struct drm_device *dev, void *data, | ||||
| 	if (!drm_core_check_feature(dev, DRIVER_MODESET)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 
 | ||||
| 	if (!dev->irq_enabled) | ||||
| 	if (!drm_dev_has_vblank(dev)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 
 | ||||
| 	crtc = drm_crtc_find(dev, file_priv, get_seq->crtc_id); | ||||
| @ -2082,7 +2091,7 @@ int drm_crtc_queue_sequence_ioctl(struct drm_device *dev, void *data, | ||||
| 	if (!drm_core_check_feature(dev, DRIVER_MODESET)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 
 | ||||
| 	if (!dev->irq_enabled) | ||||
| 	if (!drm_dev_has_vblank(dev)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 
 | ||||
| 	crtc = drm_crtc_find(dev, file_priv, queue_seq->crtc_id); | ||||
|  | ||||
| @ -190,7 +190,8 @@ int etnaviv_sched_init(struct etnaviv_gpu *gpu) | ||||
| 
 | ||||
| 	ret = drm_sched_init(&gpu->sched, &etnaviv_sched_ops, | ||||
| 			     etnaviv_hw_jobs_limit, etnaviv_job_hang_limit, | ||||
| 			     msecs_to_jiffies(500), NULL, dev_name(gpu->dev)); | ||||
| 			     msecs_to_jiffies(500), NULL, NULL, | ||||
| 			     dev_name(gpu->dev)); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
|  | ||||
| @ -300,16 +300,6 @@ static int exynos_drm_bind(struct device *dev) | ||||
| 
 | ||||
| 	drm_mode_config_reset(drm); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * enable drm irq mode. | ||||
| 	 * - with irq_enabled = true, we can use the vblank feature. | ||||
| 	 * | ||||
| 	 * P.S. note that we wouldn't use drm irq handler but | ||||
| 	 *	just specific driver own one instead because | ||||
| 	 *	drm framework supports only one irq handler. | ||||
| 	 */ | ||||
| 	drm->irq_enabled = true; | ||||
| 
 | ||||
| 	/* init kms poll for handling hpd */ | ||||
| 	drm_kms_helper_poll_init(drm); | ||||
| 
 | ||||
|  | ||||
| @ -113,11 +113,11 @@ static void oaktrail_lvds_mode_set(struct drm_encoder *encoder, | ||||
| 
 | ||||
| 	/* Find the connector we're trying to set up */ | ||||
| 	list_for_each_entry(connector, &mode_config->connector_list, head) { | ||||
| 		if (!connector->encoder || connector->encoder->crtc != crtc) | ||||
| 			continue; | ||||
| 		if (connector->encoder && connector->encoder->crtc == crtc) | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!connector) { | ||||
| 	if (list_entry_is_head(connector, &mode_config->connector_list, head)) { | ||||
| 		DRM_ERROR("Couldn't find connector when setting mode"); | ||||
| 		gma_power_end(dev); | ||||
| 		return; | ||||
|  | ||||
| @ -364,7 +364,6 @@ static void gud_debugfs_init(struct drm_minor *minor) | ||||
| static const struct drm_simple_display_pipe_funcs gud_pipe_funcs = { | ||||
| 	.check      = gud_pipe_check, | ||||
| 	.update	    = gud_pipe_update, | ||||
| 	.prepare_fb = drm_gem_simple_display_pipe_prepare_fb, | ||||
| }; | ||||
| 
 | ||||
| static const struct drm_mode_config_funcs gud_mode_config_funcs = { | ||||
| @ -394,14 +393,42 @@ static const struct drm_driver gud_drm_driver = { | ||||
| 	.minor			= 0, | ||||
| }; | ||||
| 
 | ||||
| static void gud_free_buffers_and_mutex(struct drm_device *drm, void *unused) | ||||
| static int gud_alloc_bulk_buffer(struct gud_device *gdrm) | ||||
| { | ||||
| 	struct gud_device *gdrm = to_gud_device(drm); | ||||
| 	unsigned int i, num_pages; | ||||
| 	struct page **pages; | ||||
| 	void *ptr; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	gdrm->bulk_buf = vmalloc_32(gdrm->bulk_len); | ||||
| 	if (!gdrm->bulk_buf) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	num_pages = DIV_ROUND_UP(gdrm->bulk_len, PAGE_SIZE); | ||||
| 	pages = kmalloc_array(num_pages, sizeof(struct page *), GFP_KERNEL); | ||||
| 	if (!pages) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	for (i = 0, ptr = gdrm->bulk_buf; i < num_pages; i++, ptr += PAGE_SIZE) | ||||
| 		pages[i] = vmalloc_to_page(ptr); | ||||
| 
 | ||||
| 	ret = sg_alloc_table_from_pages(&gdrm->bulk_sgt, pages, num_pages, | ||||
| 					0, gdrm->bulk_len, GFP_KERNEL); | ||||
| 	kfree(pages); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void gud_free_buffers_and_mutex(void *data) | ||||
| { | ||||
| 	struct gud_device *gdrm = data; | ||||
| 
 | ||||
| 	vfree(gdrm->compress_buf); | ||||
| 	kfree(gdrm->bulk_buf); | ||||
| 	gdrm->compress_buf = NULL; | ||||
| 	sg_free_table(&gdrm->bulk_sgt); | ||||
| 	vfree(gdrm->bulk_buf); | ||||
| 	gdrm->bulk_buf = NULL; | ||||
| 	mutex_destroy(&gdrm->ctrl_lock); | ||||
| 	mutex_destroy(&gdrm->damage_lock); | ||||
| } | ||||
| 
 | ||||
| static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id) | ||||
| @ -455,7 +482,7 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id) | ||||
| 	INIT_WORK(&gdrm->work, gud_flush_work); | ||||
| 	gud_clear_damage(gdrm); | ||||
| 
 | ||||
| 	ret = drmm_add_action_or_reset(drm, gud_free_buffers_and_mutex, NULL); | ||||
| 	ret = devm_add_action(dev, gud_free_buffers_and_mutex, gdrm); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| @ -536,24 +563,17 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id) | ||||
| 
 | ||||
| 	if (desc.max_buffer_size) | ||||
| 		max_buffer_size = le32_to_cpu(desc.max_buffer_size); | ||||
| retry: | ||||
| 	/*
 | ||||
| 	 * Use plain kmalloc here since devm_kmalloc() places struct devres at the beginning | ||||
| 	 * of the buffer it allocates. This wastes a lot of memory when allocating big buffers. | ||||
| 	 * Asking for 2M would actually allocate 4M. This would also prevent getting the biggest | ||||
| 	 * possible buffer potentially leading to split transfers. | ||||
| 	 */ | ||||
| 	gdrm->bulk_buf = kmalloc(max_buffer_size, GFP_KERNEL | __GFP_NOWARN); | ||||
| 	if (!gdrm->bulk_buf) { | ||||
| 		max_buffer_size = roundup_pow_of_two(max_buffer_size) / 2; | ||||
| 		if (max_buffer_size < SZ_512K) | ||||
| 			return -ENOMEM; | ||||
| 		goto retry; | ||||
| 	} | ||||
| 	/* Prevent a misbehaving device from allocating loads of RAM. 4096x4096@XRGB8888 = 64 MB */ | ||||
| 	if (max_buffer_size > SZ_64M) | ||||
| 		max_buffer_size = SZ_64M; | ||||
| 
 | ||||
| 	gdrm->bulk_pipe = usb_sndbulkpipe(interface_to_usbdev(intf), usb_endpoint_num(bulk_out)); | ||||
| 	gdrm->bulk_len = max_buffer_size; | ||||
| 
 | ||||
| 	ret = gud_alloc_bulk_buffer(gdrm); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	if (gdrm->compression & GUD_COMPRESSION_LZ4) { | ||||
| 		gdrm->lz4_comp_mem = devm_kmalloc(dev, LZ4_MEM_COMPRESS, GFP_KERNEL); | ||||
| 		if (!gdrm->lz4_comp_mem) | ||||
| @ -640,6 +660,7 @@ static int gud_resume(struct usb_interface *intf) | ||||
| 
 | ||||
| static const struct usb_device_id gud_id_table[] = { | ||||
| 	{ USB_DEVICE_INTERFACE_CLASS(0x1d50, 0x614d, USB_CLASS_VENDOR_SPEC) }, | ||||
| 	{ USB_DEVICE_INTERFACE_CLASS(0x16d0, 0x10a9, USB_CLASS_VENDOR_SPEC) }, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| 
 | ||||
| #include <linux/list.h> | ||||
| #include <linux/mutex.h> | ||||
| #include <linux/scatterlist.h> | ||||
| #include <linux/usb.h> | ||||
| #include <linux/workqueue.h> | ||||
| #include <uapi/drm/drm_fourcc.h> | ||||
| @ -26,6 +27,7 @@ struct gud_device { | ||||
| 	unsigned int bulk_pipe; | ||||
| 	void *bulk_buf; | ||||
| 	size_t bulk_len; | ||||
| 	struct sg_table bulk_sgt; | ||||
| 
 | ||||
| 	u8 compression; | ||||
| 	void *lz4_comp_mem; | ||||
|  | ||||
| @ -23,6 +23,19 @@ | ||||
| 
 | ||||
| #include "gud_internal.h" | ||||
| 
 | ||||
| /*
 | ||||
|  * Some userspace rendering loops runs all displays in the same loop. | ||||
|  * This means that a fast display will have to wait for a slow one. | ||||
|  * For this reason gud does flushing asynchronous by default. | ||||
|  * The down side is that in e.g. a single display setup userspace thinks | ||||
|  * the display is insanely fast since the driver reports back immediately | ||||
|  * that the flush/pageflip is done. This wastes CPU and power. | ||||
|  * Such users might want to set this module parameter to false. | ||||
|  */ | ||||
| static bool gud_async_flush = true; | ||||
| module_param_named(async_flush, gud_async_flush, bool, 0644); | ||||
| MODULE_PARM_DESC(async_flush, "Enable asynchronous flushing [default=true]"); | ||||
| 
 | ||||
| /*
 | ||||
|  * FIXME: The driver is probably broken on Big Endian machines. | ||||
|  * See discussion: | ||||
| @ -220,13 +233,51 @@ vunmap: | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| struct gud_usb_bulk_context { | ||||
| 	struct timer_list timer; | ||||
| 	struct usb_sg_request sgr; | ||||
| }; | ||||
| 
 | ||||
| static void gud_usb_bulk_timeout(struct timer_list *t) | ||||
| { | ||||
| 	struct gud_usb_bulk_context *ctx = from_timer(ctx, t, timer); | ||||
| 
 | ||||
| 	usb_sg_cancel(&ctx->sgr); | ||||
| } | ||||
| 
 | ||||
| static int gud_usb_bulk(struct gud_device *gdrm, size_t len) | ||||
| { | ||||
| 	struct gud_usb_bulk_context ctx; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = usb_sg_init(&ctx.sgr, gud_to_usb_device(gdrm), gdrm->bulk_pipe, 0, | ||||
| 			  gdrm->bulk_sgt.sgl, gdrm->bulk_sgt.nents, len, GFP_KERNEL); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	timer_setup_on_stack(&ctx.timer, gud_usb_bulk_timeout, 0); | ||||
| 	mod_timer(&ctx.timer, jiffies + msecs_to_jiffies(3000)); | ||||
| 
 | ||||
| 	usb_sg_wait(&ctx.sgr); | ||||
| 
 | ||||
| 	if (!del_timer_sync(&ctx.timer)) | ||||
| 		ret = -ETIMEDOUT; | ||||
| 	else if (ctx.sgr.status < 0) | ||||
| 		ret = ctx.sgr.status; | ||||
| 	else if (ctx.sgr.bytes != len) | ||||
| 		ret = -EIO; | ||||
| 
 | ||||
| 	destroy_timer_on_stack(&ctx.timer); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int gud_flush_rect(struct gud_device *gdrm, struct drm_framebuffer *fb, | ||||
| 			  const struct drm_format_info *format, struct drm_rect *rect) | ||||
| { | ||||
| 	struct usb_device *usb = gud_to_usb_device(gdrm); | ||||
| 	struct gud_set_buffer_req req; | ||||
| 	int ret, actual_length; | ||||
| 	size_t len, trlen; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	drm_dbg(&gdrm->drm, "Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); | ||||
| 
 | ||||
| @ -255,10 +306,7 @@ static int gud_flush_rect(struct gud_device *gdrm, struct drm_framebuffer *fb, | ||||
| 			return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = usb_bulk_msg(usb, gdrm->bulk_pipe, gdrm->bulk_buf, trlen, | ||||
| 			   &actual_length, msecs_to_jiffies(3000)); | ||||
| 	if (!ret && trlen != actual_length) | ||||
| 		ret = -EIO; | ||||
| 	ret = gud_usb_bulk(gdrm, trlen); | ||||
| 	if (ret) | ||||
| 		gdrm->stats_num_errors++; | ||||
| 
 | ||||
| @ -543,6 +591,8 @@ void gud_pipe_update(struct drm_simple_display_pipe *pipe, | ||||
| 		if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) | ||||
| 			drm_rect_init(&damage, 0, 0, fb->width, fb->height); | ||||
| 		gud_fb_queue_damage(gdrm, fb, &damage); | ||||
| 		if (!gud_async_flush) | ||||
| 			flush_work(&gdrm->work); | ||||
| 	} | ||||
| 
 | ||||
| 	if (!crtc->state->enable) | ||||
|  | ||||
| @ -152,8 +152,7 @@ static const struct drm_plane_funcs hibmc_plane_funcs = { | ||||
| }; | ||||
| 
 | ||||
| static const struct drm_plane_helper_funcs hibmc_plane_helper_funcs = { | ||||
| 	.prepare_fb	= drm_gem_vram_plane_helper_prepare_fb, | ||||
| 	.cleanup_fb	= drm_gem_vram_plane_helper_cleanup_fb, | ||||
| 	DRM_GEM_VRAM_PLANE_HELPER_FUNCS, | ||||
| 	.atomic_check = hibmc_plane_atomic_check, | ||||
| 	.atomic_update = hibmc_plane_atomic_update, | ||||
| }; | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_gem_framebuffer_helper.h> | ||||
| #include <drm/drm_gem_vram_helper.h> | ||||
| #include <drm/drm_irq.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_vblank.h> | ||||
| 
 | ||||
| @ -28,7 +27,7 @@ | ||||
| 
 | ||||
| DEFINE_DRM_GEM_FOPS(hibmc_fops); | ||||
| 
 | ||||
| static irqreturn_t hibmc_drm_interrupt(int irq, void *arg) | ||||
| static irqreturn_t hibmc_interrupt(int irq, void *arg) | ||||
| { | ||||
| 	struct drm_device *dev = (struct drm_device *)arg; | ||||
| 	struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); | ||||
| @ -63,7 +62,6 @@ static const struct drm_driver hibmc_driver = { | ||||
| 	.dumb_create            = hibmc_dumb_create, | ||||
| 	.dumb_map_offset        = drm_gem_ttm_dumb_map_offset, | ||||
| 	.gem_prime_mmap		= drm_gem_prime_mmap, | ||||
| 	.irq_handler		= hibmc_drm_interrupt, | ||||
| }; | ||||
| 
 | ||||
| static int __maybe_unused hibmc_pm_suspend(struct device *dev) | ||||
| @ -251,10 +249,12 @@ static int hibmc_hw_init(struct hibmc_drm_private *priv) | ||||
| 
 | ||||
| static int hibmc_unload(struct drm_device *dev) | ||||
| { | ||||
| 	struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); | ||||
| 	struct pci_dev *pdev = to_pci_dev(dev->dev); | ||||
| 
 | ||||
| 	drm_atomic_helper_shutdown(dev); | ||||
| 
 | ||||
| 	if (dev->irq_enabled) | ||||
| 		drm_irq_uninstall(dev); | ||||
| 	free_irq(pdev->irq, dev); | ||||
| 
 | ||||
| 	pci_disable_msi(to_pci_dev(dev->dev)); | ||||
| 
 | ||||
| @ -291,7 +291,9 @@ static int hibmc_load(struct drm_device *dev) | ||||
| 	if (ret) { | ||||
| 		drm_warn(dev, "enabling MSI failed: %d\n", ret); | ||||
| 	} else { | ||||
| 		ret = drm_irq_install(dev, pdev->irq); | ||||
| 		/* PCI devices require shared interrupts. */ | ||||
| 		ret = request_irq(pdev->irq, hibmc_interrupt, IRQF_SHARED, | ||||
| 				  dev->driver->name, dev); | ||||
| 		if (ret) | ||||
| 			drm_warn(dev, "install irq failed: %d\n", ret); | ||||
| 	} | ||||
| @ -314,7 +316,7 @@ static int hibmc_pci_probe(struct pci_dev *pdev, | ||||
| 	struct drm_device *dev; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "hibmcdrmfb"); | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &hibmc_driver); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
|  | ||||
| @ -185,8 +185,6 @@ static int kirin_drm_kms_init(struct drm_device *dev, | ||||
| 		DRM_ERROR("failed to initialize vblank.\n"); | ||||
| 		goto err_unbind_all; | ||||
| 	} | ||||
| 	/* with irq_enabled = true, we can use the vblank feature. */ | ||||
| 	dev->irq_enabled = true; | ||||
| 
 | ||||
| 	/* reset all the states of crtc/plane/encoder/connector */ | ||||
| 	drm_mode_config_reset(dev); | ||||
|  | ||||
| @ -82,7 +82,7 @@ static int hyperv_setup_gen1(struct hyperv_drm_device *hv) | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "hypervdrmfb"); | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &hyperv_driver); | ||||
| 	if (ret) { | ||||
| 		drm_err(dev, "Not able to remove boot fb\n"); | ||||
| 		return ret; | ||||
| @ -127,7 +127,7 @@ static int hyperv_setup_gen2(struct hyperv_drm_device *hv, | ||||
| 	drm_aperture_remove_conflicting_framebuffers(screen_info.lfb_base, | ||||
| 						     screen_info.lfb_size, | ||||
| 						     false, | ||||
| 						     "hypervdrmfb"); | ||||
| 						     &hyperv_driver); | ||||
| 
 | ||||
| 	hv->fb_size = (unsigned long)hv->mmio_megabytes * 1024 * 1024; | ||||
| 
 | ||||
|  | ||||
| @ -155,6 +155,7 @@ gem-y += \ | ||||
| 	gem/i915_gem_stolen.o \
 | ||||
| 	gem/i915_gem_throttle.o \
 | ||||
| 	gem/i915_gem_tiling.o \
 | ||||
| 	gem/i915_gem_ttm.o \
 | ||||
| 	gem/i915_gem_userptr.o \
 | ||||
| 	gem/i915_gem_wait.o \
 | ||||
| 	gem/i915_gemfs.o | ||||
|  | ||||
| @ -11778,7 +11778,7 @@ intel_user_framebuffer_create(struct drm_device *dev, | ||||
| 
 | ||||
| 	/* object is backed with LMEM for discrete */ | ||||
| 	i915 = to_i915(obj->base.dev); | ||||
| 	if (HAS_LMEM(i915) && !i915_gem_object_is_lmem(obj)) { | ||||
| 	if (HAS_LMEM(i915) && !i915_gem_object_validates_to_lmem(obj)) { | ||||
| 		/* object is "remote", not in local memory */ | ||||
| 		i915_gem_object_put(obj); | ||||
| 		return ERR_PTR(-EREMOTE); | ||||
|  | ||||
| @ -314,7 +314,7 @@ struct intel_panel { | ||||
| 		/* DPCD backlight */ | ||||
| 		union { | ||||
| 			struct { | ||||
| 				u8 pwmgen_bit_count; | ||||
| 				struct drm_edp_backlight_info info; | ||||
| 			} vesa; | ||||
| 			struct { | ||||
| 				bool sdr_uses_aux; | ||||
|  | ||||
| @ -107,7 +107,7 @@ intel_dp_aux_supports_hdr_backlight(struct intel_connector *connector) | ||||
| 	u8 tcon_cap[4]; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_read(aux, INTEL_EDP_HDR_TCON_CAP0, tcon_cap, sizeof(tcon_cap)); | ||||
| 	if (ret < 0) | ||||
| 	if (ret != sizeof(tcon_cap)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (!(tcon_cap[1] & INTEL_EDP_HDR_TCON_BRIGHTNESS_NITS_CAP)) | ||||
| @ -137,7 +137,7 @@ intel_dp_aux_hdr_get_backlight(struct intel_connector *connector, enum pipe pipe | ||||
| 	u8 tmp; | ||||
| 	u8 buf[2] = { 0 }; | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) < 0) { | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) != 1) { | ||||
| 		drm_err(&i915->drm, "Failed to read current backlight mode from DPCD\n"); | ||||
| 		return 0; | ||||
| 	} | ||||
| @ -153,7 +153,8 @@ intel_dp_aux_hdr_get_backlight(struct intel_connector *connector, enum pipe pipe | ||||
| 		return panel->backlight.max; | ||||
| 	} | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, sizeof(buf)) < 0) { | ||||
| 	if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, | ||||
| 			     sizeof(buf)) != sizeof(buf)) { | ||||
| 		drm_err(&i915->drm, "Failed to read brightness from DPCD\n"); | ||||
| 		return 0; | ||||
| 	} | ||||
| @ -172,7 +173,8 @@ intel_dp_aux_hdr_set_aux_backlight(const struct drm_connector_state *conn_state, | ||||
| 	buf[0] = level & 0xFF; | ||||
| 	buf[1] = (level & 0xFF00) >> 8; | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, 4) < 0) | ||||
| 	if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, | ||||
| 			      sizeof(buf)) != sizeof(buf)) | ||||
| 		drm_err(dev, "Failed to write brightness level to DPCD\n"); | ||||
| } | ||||
| 
 | ||||
| @ -203,7 +205,7 @@ intel_dp_aux_hdr_enable_backlight(const struct intel_crtc_state *crtc_state, | ||||
| 	u8 old_ctrl, ctrl; | ||||
| 
 | ||||
| 	ret = drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &old_ctrl); | ||||
| 	if (ret < 0) { | ||||
| 	if (ret != 1) { | ||||
| 		drm_err(&i915->drm, "Failed to read current backlight control mode: %d\n", ret); | ||||
| 		return; | ||||
| 	} | ||||
| @ -221,7 +223,7 @@ intel_dp_aux_hdr_enable_backlight(const struct intel_crtc_state *crtc_state, | ||||
| 	} | ||||
| 
 | ||||
| 	if (ctrl != old_ctrl) | ||||
| 		if (drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) < 0) | ||||
| 		if (drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) != 1) | ||||
| 			drm_err(&i915->drm, "Failed to configure DPCD brightness controls\n"); | ||||
| } | ||||
| 
 | ||||
| @ -268,153 +270,19 @@ intel_dp_aux_hdr_setup_backlight(struct intel_connector *connector, enum pipe pi | ||||
| } | ||||
| 
 | ||||
| /* VESA backlight callbacks */ | ||||
| static void set_vesa_backlight_enable(struct intel_dp *intel_dp, bool enable) | ||||
| { | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 	u8 reg_val = 0; | ||||
| 
 | ||||
| 	/* Early return when display use other mechanism to enable backlight. */ | ||||
| 	if (!(intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, | ||||
| 			      ®_val) < 0) { | ||||
| 		drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n", | ||||
| 			    DP_EDP_DISPLAY_CONTROL_REGISTER); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (enable) | ||||
| 		reg_val |= DP_EDP_BACKLIGHT_ENABLE; | ||||
| 	else | ||||
| 		reg_val &= ~(DP_EDP_BACKLIGHT_ENABLE); | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, | ||||
| 			       reg_val) != 1) { | ||||
| 		drm_dbg_kms(&i915->drm, "Failed to %s aux backlight\n", | ||||
| 			    enabledisable(enable)); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool intel_dp_aux_vesa_backlight_dpcd_mode(struct intel_connector *connector) | ||||
| { | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 	u8 mode_reg; | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, | ||||
| 			      DP_EDP_BACKLIGHT_MODE_SET_REGISTER, | ||||
| 			      &mode_reg) != 1) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "Failed to read the DPCD register 0x%x\n", | ||||
| 			    DP_EDP_BACKLIGHT_MODE_SET_REGISTER); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	return (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) == | ||||
| 	       DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Read the current backlight value from DPCD register(s) based | ||||
|  * on if 8-bit(MSB) or 16-bit(MSB and LSB) values are supported | ||||
|  */ | ||||
| static u32 intel_dp_aux_vesa_get_backlight(struct intel_connector *connector, enum pipe unused) | ||||
| { | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 	u8 read_val[2] = { 0x0 }; | ||||
| 	u16 level = 0; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If we're not in DPCD control mode yet, the programmed brightness | ||||
| 	 * value is meaningless and we should assume max brightness | ||||
| 	 */ | ||||
| 	if (!intel_dp_aux_vesa_backlight_dpcd_mode(connector)) | ||||
| 		return connector->panel.backlight.max; | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, | ||||
| 			     &read_val, sizeof(read_val)) < 0) { | ||||
| 		drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n", | ||||
| 			    DP_EDP_BACKLIGHT_BRIGHTNESS_MSB); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	level = read_val[0]; | ||||
| 	if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) | ||||
| 		level = (read_val[0] << 8 | read_val[1]); | ||||
| 
 | ||||
| 	return level; | ||||
| 	return connector->panel.backlight.level; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Sends the current backlight level over the aux channel, checking if its using | ||||
|  * 8-bit or 16 bit value (MSB and LSB) | ||||
|  */ | ||||
| static void | ||||
| intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, | ||||
| 				u32 level) | ||||
| intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, u32 level) | ||||
| { | ||||
| 	struct intel_connector *connector = to_intel_connector(conn_state->connector); | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 	u8 vals[2] = { 0x0 }; | ||||
| 	struct intel_panel *panel = &connector->panel; | ||||
| 	struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); | ||||
| 
 | ||||
| 	vals[0] = level; | ||||
| 
 | ||||
| 	/* Write the MSB and/or LSB */ | ||||
| 	if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) { | ||||
| 		vals[0] = (level & 0xFF00) >> 8; | ||||
| 		vals[1] = (level & 0xFF); | ||||
| 	} | ||||
| 	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, | ||||
| 			      vals, sizeof(vals)) < 0) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "Failed to write aux backlight level\n"); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Set PWM Frequency divider to match desired frequency in vbt. | ||||
|  * The PWM Frequency is calculated as 27Mhz / (F x P). | ||||
|  * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the | ||||
|  *             EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) | ||||
|  * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the | ||||
|  *             EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) | ||||
|  */ | ||||
| static bool intel_dp_aux_vesa_set_pwm_freq(struct intel_connector *connector) | ||||
| { | ||||
| 	struct drm_i915_private *dev_priv = to_i915(connector->base.dev); | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	const u8 pn = connector->panel.backlight.edp.vesa.pwmgen_bit_count; | ||||
| 	int freq, fxp, f, fxp_actual, fxp_min, fxp_max; | ||||
| 
 | ||||
| 	freq = dev_priv->vbt.backlight.pwm_freq_hz; | ||||
| 	if (!freq) { | ||||
| 		drm_dbg_kms(&dev_priv->drm, | ||||
| 			    "Use panel default backlight frequency\n"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq); | ||||
| 	f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); | ||||
| 	fxp_actual = f << pn; | ||||
| 
 | ||||
| 	/* Ensure frequency is within 25% of desired value */ | ||||
| 	fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); | ||||
| 	fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); | ||||
| 
 | ||||
| 	if (fxp_min > fxp_actual || fxp_actual > fxp_max) { | ||||
| 		drm_dbg_kms(&dev_priv->drm, "Actual frequency out of range\n"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_writeb(&intel_dp->aux, | ||||
| 			       DP_EDP_BACKLIGHT_FREQ_SET, (u8) f) < 0) { | ||||
| 		drm_dbg_kms(&dev_priv->drm, | ||||
| 			    "Failed to write aux backlight freq\n"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| 	drm_edp_backlight_set_level(&intel_dp->aux, &panel->backlight.edp.vesa.info, level); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -422,159 +290,46 @@ intel_dp_aux_vesa_enable_backlight(const struct intel_crtc_state *crtc_state, | ||||
| 				   const struct drm_connector_state *conn_state, u32 level) | ||||
| { | ||||
| 	struct intel_connector *connector = to_intel_connector(conn_state->connector); | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 	struct intel_panel *panel = &connector->panel; | ||||
| 	u8 dpcd_buf, new_dpcd_buf, edp_backlight_mode; | ||||
| 	u8 pwmgen_bit_count = panel->backlight.edp.vesa.pwmgen_bit_count; | ||||
| 	struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, | ||||
| 			DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf) != 1) { | ||||
| 		drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n", | ||||
| 			    DP_EDP_BACKLIGHT_MODE_SET_REGISTER); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	new_dpcd_buf = dpcd_buf; | ||||
| 	edp_backlight_mode = dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; | ||||
| 
 | ||||
| 	switch (edp_backlight_mode) { | ||||
| 	case DP_EDP_BACKLIGHT_CONTROL_MODE_PWM: | ||||
| 	case DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET: | ||||
| 	case DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT: | ||||
| 		new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; | ||||
| 		new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; | ||||
| 
 | ||||
| 		if (drm_dp_dpcd_writeb(&intel_dp->aux, | ||||
| 				       DP_EDP_PWMGEN_BIT_COUNT, | ||||
| 				       pwmgen_bit_count) < 0) | ||||
| 			drm_dbg_kms(&i915->drm, | ||||
| 				    "Failed to write aux pwmgen bit count\n"); | ||||
| 
 | ||||
| 		break; | ||||
| 
 | ||||
| 	/* Do nothing when it is already DPCD mode */ | ||||
| 	case DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD: | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) | ||||
| 		if (intel_dp_aux_vesa_set_pwm_freq(connector)) | ||||
| 			new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; | ||||
| 
 | ||||
| 	if (new_dpcd_buf != dpcd_buf) { | ||||
| 		if (drm_dp_dpcd_writeb(&intel_dp->aux, | ||||
| 			DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf) < 0) { | ||||
| 			drm_dbg_kms(&i915->drm, | ||||
| 				    "Failed to write aux backlight mode\n"); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	intel_dp_aux_vesa_set_backlight(conn_state, level); | ||||
| 	set_vesa_backlight_enable(intel_dp, true); | ||||
| 	drm_edp_backlight_enable(&intel_dp->aux, &panel->backlight.edp.vesa.info, level); | ||||
| } | ||||
| 
 | ||||
| static void intel_dp_aux_vesa_disable_backlight(const struct drm_connector_state *old_conn_state, | ||||
| 						u32 level) | ||||
| { | ||||
| 	set_vesa_backlight_enable(enc_to_intel_dp(to_intel_encoder(old_conn_state->best_encoder)), | ||||
| 				  false); | ||||
| 	struct intel_connector *connector = to_intel_connector(old_conn_state->connector); | ||||
| 	struct intel_panel *panel = &connector->panel; | ||||
| 	struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); | ||||
| 
 | ||||
| 	drm_edp_backlight_disable(&intel_dp->aux, &panel->backlight.edp.vesa.info); | ||||
| } | ||||
| 
 | ||||
| static u32 intel_dp_aux_vesa_calc_max_backlight(struct intel_connector *connector) | ||||
| static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, enum pipe pipe) | ||||
| { | ||||
| 	struct drm_i915_private *i915 = to_i915(connector->base.dev); | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	struct intel_panel *panel = &connector->panel; | ||||
| 	u32 max_backlight = 0; | ||||
| 	int freq, fxp, fxp_min, fxp_max, fxp_actual, f = 1; | ||||
| 	u8 pn, pn_min, pn_max; | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 	u16 current_level; | ||||
| 	u8 current_mode; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_PWMGEN_BIT_COUNT, &pn) == 1) { | ||||
| 		pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; | ||||
| 		max_backlight = (1 << pn) - 1; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Find desired value of (F x P)
 | ||||
| 	 * Note that, if F x P is out of supported range, the maximum value or | ||||
| 	 * minimum value will applied automatically. So no need to check that. | ||||
| 	 */ | ||||
| 	freq = i915->vbt.backlight.pwm_freq_hz; | ||||
| 	drm_dbg_kms(&i915->drm, "VBT defined backlight frequency %u Hz\n", | ||||
| 		    freq); | ||||
| 	if (!freq) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "Use panel default backlight frequency\n"); | ||||
| 		return max_backlight; | ||||
| 	} | ||||
| 
 | ||||
| 	fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq); | ||||
| 
 | ||||
| 	/* Use highest possible value of Pn for more granularity of brightness
 | ||||
| 	 * adjustment while satifying the conditions below. | ||||
| 	 * - Pn is in the range of Pn_min and Pn_max | ||||
| 	 * - F is in the range of 1 and 255 | ||||
| 	 * - FxP is within 25% of desired value. | ||||
| 	 *   Note: 25% is arbitrary value and may need some tweak. | ||||
| 	 */ | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, | ||||
| 			      DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min) != 1) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "Failed to read pwmgen bit count cap min\n"); | ||||
| 		return max_backlight; | ||||
| 	} | ||||
| 	if (drm_dp_dpcd_readb(&intel_dp->aux, | ||||
| 			      DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max) != 1) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "Failed to read pwmgen bit count cap max\n"); | ||||
| 		return max_backlight; | ||||
| 	} | ||||
| 	pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; | ||||
| 	pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; | ||||
| 
 | ||||
| 	fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); | ||||
| 	fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); | ||||
| 	if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "VBT defined backlight frequency out of range\n"); | ||||
| 		return max_backlight; | ||||
| 	} | ||||
| 
 | ||||
| 	for (pn = pn_max; pn >= pn_min; pn--) { | ||||
| 		f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); | ||||
| 		fxp_actual = f << pn; | ||||
| 		if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	drm_dbg_kms(&i915->drm, "Using eDP pwmgen bit count of %d\n", pn); | ||||
| 	if (drm_dp_dpcd_writeb(&intel_dp->aux, | ||||
| 			       DP_EDP_PWMGEN_BIT_COUNT, pn) < 0) { | ||||
| 		drm_dbg_kms(&i915->drm, | ||||
| 			    "Failed to write aux pwmgen bit count\n"); | ||||
| 		return max_backlight; | ||||
| 	} | ||||
| 	panel->backlight.edp.vesa.pwmgen_bit_count = pn; | ||||
| 
 | ||||
| 	max_backlight = (1 << pn) - 1; | ||||
| 
 | ||||
| 	return max_backlight; | ||||
| } | ||||
| 
 | ||||
| static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, | ||||
| 					     enum pipe pipe) | ||||
| { | ||||
| 	struct intel_panel *panel = &connector->panel; | ||||
| 
 | ||||
| 	panel->backlight.max = intel_dp_aux_vesa_calc_max_backlight(connector); | ||||
| 	if (!panel->backlight.max) | ||||
| 		return -ENODEV; | ||||
| 	ret = drm_edp_backlight_init(&intel_dp->aux, &panel->backlight.edp.vesa.info, | ||||
| 				     i915->vbt.backlight.pwm_freq_hz, intel_dp->edp_dpcd, | ||||
| 				     ¤t_level, ¤t_mode); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	panel->backlight.max = panel->backlight.edp.vesa.info.max; | ||||
| 	panel->backlight.min = 0; | ||||
| 	panel->backlight.level = intel_dp_aux_vesa_get_backlight(connector, pipe); | ||||
| 	panel->backlight.enabled = intel_dp_aux_vesa_backlight_dpcd_mode(connector) && | ||||
| 				   panel->backlight.level != 0; | ||||
| 	if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { | ||||
| 		panel->backlight.level = current_level; | ||||
| 		panel->backlight.enabled = panel->backlight.level != 0; | ||||
| 	} else { | ||||
| 		panel->backlight.level = panel->backlight.max; | ||||
| 		panel->backlight.enabled = false; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| @ -585,16 +340,12 @@ intel_dp_aux_supports_vesa_backlight(struct intel_connector *connector) | ||||
| 	struct intel_dp *intel_dp = intel_attached_dp(connector); | ||||
| 	struct drm_i915_private *i915 = dp_to_i915(intel_dp); | ||||
| 
 | ||||
| 	/* Check the eDP Display control capabilities registers to determine if
 | ||||
| 	 * the panel can support backlight control over the aux channel. | ||||
| 	 * | ||||
| 	 * TODO: We currently only support AUX only backlight configurations, not backlights which | ||||
| 	/* TODO: We currently only support AUX only backlight configurations, not backlights which
 | ||||
| 	 * require a mix of PWM and AUX controls to work. In the mean time, these machines typically | ||||
| 	 * work just fine using normal PWM controls anyway. | ||||
| 	 */ | ||||
| 	if (intel_dp->edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP && | ||||
| 	    (intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) && | ||||
| 	    (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) { | ||||
| 	if ((intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) && | ||||
| 	    drm_edp_backlight_supported(intel_dp->edp_dpcd)) { | ||||
| 		drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n"); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| @ -85,13 +85,10 @@ i915_gem_setup(struct drm_i915_gem_object *obj, u64 size) | ||||
| 		return -E2BIG; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * For now resort to CPU based clearing for device local-memory, in the | ||||
| 	 * near future this will use the blitter engine for accelerated, GPU | ||||
| 	 * based clearing. | ||||
| 	 * I915_BO_ALLOC_USER will make sure the object is cleared before | ||||
| 	 * any user access. | ||||
| 	 */ | ||||
| 	flags = 0; | ||||
| 	if (mr->type == INTEL_MEMORY_LOCAL) | ||||
| 		flags = I915_BO_ALLOC_CPU_CLEAR; | ||||
| 	flags = I915_BO_ALLOC_USER; | ||||
| 
 | ||||
| 	ret = mr->ops->init_object(mr, obj, size, flags); | ||||
| 	if (ret) | ||||
|  | ||||
| @ -2983,7 +2983,7 @@ __free_fence_array(struct eb_fence *fences, unsigned int n) | ||||
| 	while (n--) { | ||||
| 		drm_syncobj_put(ptr_mask_bits(fences[n].syncobj, 2)); | ||||
| 		dma_fence_put(fences[n].dma_fence); | ||||
| 		kfree(fences[n].chain_fence); | ||||
| 		dma_fence_chain_free(fences[n].chain_fence); | ||||
| 	} | ||||
| 	kvfree(fences); | ||||
| } | ||||
| @ -3097,9 +3097,7 @@ add_timeline_fence_array(struct i915_execbuffer *eb, | ||||
| 				return -EINVAL; | ||||
| 			} | ||||
| 
 | ||||
| 			f->chain_fence = | ||||
| 				kmalloc(sizeof(*f->chain_fence), | ||||
| 					GFP_KERNEL); | ||||
| 			f->chain_fence = dma_fence_chain_alloc(); | ||||
| 			if (!f->chain_fence) { | ||||
| 				drm_syncobj_put(syncobj); | ||||
| 				dma_fence_put(fence); | ||||
|  | ||||
| @ -4,74 +4,10 @@ | ||||
|  */ | ||||
| 
 | ||||
| #include "intel_memory_region.h" | ||||
| #include "intel_region_ttm.h" | ||||
| #include "gem/i915_gem_region.h" | ||||
| #include "gem/i915_gem_lmem.h" | ||||
| #include "i915_drv.h" | ||||
| 
 | ||||
| static void lmem_put_pages(struct drm_i915_gem_object *obj, | ||||
| 			   struct sg_table *pages) | ||||
| { | ||||
| 	intel_region_ttm_node_free(obj->mm.region, obj->mm.st_mm_node); | ||||
| 	obj->mm.dirty = false; | ||||
| 	sg_free_table(pages); | ||||
| 	kfree(pages); | ||||
| } | ||||
| 
 | ||||
| static int lmem_get_pages(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	unsigned int flags; | ||||
| 	struct sg_table *pages; | ||||
| 
 | ||||
| 	flags = I915_ALLOC_MIN_PAGE_SIZE; | ||||
| 	if (obj->flags & I915_BO_ALLOC_CONTIGUOUS) | ||||
| 		flags |= I915_ALLOC_CONTIGUOUS; | ||||
| 
 | ||||
| 	obj->mm.st_mm_node = intel_region_ttm_node_alloc(obj->mm.region, | ||||
| 							 obj->base.size, | ||||
| 							 flags); | ||||
| 	if (IS_ERR(obj->mm.st_mm_node)) | ||||
| 		return PTR_ERR(obj->mm.st_mm_node); | ||||
| 
 | ||||
| 	/* Range manager is always contigous */ | ||||
| 	if (obj->mm.region->is_range_manager) | ||||
| 		obj->flags |= I915_BO_ALLOC_CONTIGUOUS; | ||||
| 	pages = intel_region_ttm_node_to_st(obj->mm.region, obj->mm.st_mm_node); | ||||
| 	if (IS_ERR(pages)) { | ||||
| 		intel_region_ttm_node_free(obj->mm.region, obj->mm.st_mm_node); | ||||
| 		return PTR_ERR(pages); | ||||
| 	} | ||||
| 
 | ||||
| 	__i915_gem_object_set_pages(obj, pages, i915_sg_dma_sizes(pages->sgl)); | ||||
| 
 | ||||
| 	if (obj->flags & I915_BO_ALLOC_CPU_CLEAR) { | ||||
| 		void __iomem *vaddr = | ||||
| 			i915_gem_object_lmem_io_map(obj, 0, obj->base.size); | ||||
| 
 | ||||
| 		if (!vaddr) { | ||||
| 			struct sg_table *pages = | ||||
| 				__i915_gem_object_unset_pages(obj); | ||||
| 
 | ||||
| 			if (!IS_ERR_OR_NULL(pages)) | ||||
| 				lmem_put_pages(obj, pages); | ||||
| 		} | ||||
| 
 | ||||
| 		memset_io(vaddr, 0, obj->base.size); | ||||
| 		io_mapping_unmap(vaddr); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| const struct drm_i915_gem_object_ops i915_gem_lmem_obj_ops = { | ||||
| 	.name = "i915_gem_object_lmem", | ||||
| 	.flags = I915_GEM_OBJECT_HAS_IOMEM, | ||||
| 
 | ||||
| 	.get_pages = lmem_get_pages, | ||||
| 	.put_pages = lmem_put_pages, | ||||
| 	.release = i915_gem_object_release_memory_region, | ||||
| }; | ||||
| 
 | ||||
| void __iomem * | ||||
| i915_gem_object_lmem_io_map(struct drm_i915_gem_object *obj, | ||||
| 			    unsigned long n, | ||||
| @ -87,10 +23,50 @@ i915_gem_object_lmem_io_map(struct drm_i915_gem_object *obj, | ||||
| 	return io_mapping_map_wc(&obj->mm.region->iomap, offset, size); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_gem_object_validates_to_lmem - Whether the object is resident in | ||||
|  * lmem when pages are present. | ||||
|  * @obj: The object to check. | ||||
|  * | ||||
|  * Migratable objects residency may change from under us if the object is | ||||
|  * not pinned or locked. This function is intended to be used to check whether | ||||
|  * the object can only reside in lmem when pages are present. | ||||
|  * | ||||
|  * Return: Whether the object is always resident in lmem when pages are | ||||
|  * present. | ||||
|  */ | ||||
| bool i915_gem_object_validates_to_lmem(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct intel_memory_region *mr = READ_ONCE(obj->mm.region); | ||||
| 
 | ||||
| 	return !i915_gem_object_migratable(obj) && | ||||
| 		mr && (mr->type == INTEL_MEMORY_LOCAL || | ||||
| 		       mr->type == INTEL_MEMORY_STOLEN_LOCAL); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_gem_object_is_lmem - Whether the object is resident in | ||||
|  * lmem | ||||
|  * @obj: The object to check. | ||||
|  * | ||||
|  * Even if an object is allowed to migrate and change memory region, | ||||
|  * this function checks whether it will always be present in lmem when | ||||
|  * valid *or* if that's not the case, whether it's currently resident in lmem. | ||||
|  * For migratable and evictable objects, the latter only makes sense when | ||||
|  * the object is locked. | ||||
|  * | ||||
|  * Return: Whether the object migratable but resident in lmem, or not | ||||
|  * migratable and will be present in lmem when valid. | ||||
|  */ | ||||
| bool i915_gem_object_is_lmem(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct intel_memory_region *mr = obj->mm.region; | ||||
| 	struct intel_memory_region *mr = READ_ONCE(obj->mm.region); | ||||
| 
 | ||||
| #ifdef CONFIG_LOCKDEP | ||||
| 	if (i915_gem_object_migratable(obj) && | ||||
| 	    i915_gem_object_evictable(obj)) | ||||
| 		assert_object_held(obj); | ||||
| #endif | ||||
| 	return mr && (mr->type == INTEL_MEMORY_LOCAL || | ||||
| 		      mr->type == INTEL_MEMORY_STOLEN_LOCAL); | ||||
| } | ||||
| @ -103,23 +79,3 @@ i915_gem_object_create_lmem(struct drm_i915_private *i915, | ||||
| 	return i915_gem_object_create_region(i915->mm.regions[INTEL_REGION_LMEM], | ||||
| 					     size, flags); | ||||
| } | ||||
| 
 | ||||
| int __i915_gem_lmem_object_init(struct intel_memory_region *mem, | ||||
| 				struct drm_i915_gem_object *obj, | ||||
| 				resource_size_t size, | ||||
| 				unsigned int flags) | ||||
| { | ||||
| 	static struct lock_class_key lock_class; | ||||
| 	struct drm_i915_private *i915 = mem->i915; | ||||
| 
 | ||||
| 	drm_gem_private_object_init(&i915->drm, &obj->base, size); | ||||
| 	i915_gem_object_init(obj, &i915_gem_lmem_obj_ops, &lock_class, flags); | ||||
| 
 | ||||
| 	obj->read_domains = I915_GEM_DOMAIN_WC | I915_GEM_DOMAIN_GTT; | ||||
| 
 | ||||
| 	i915_gem_object_set_cache_coherency(obj, I915_CACHE_NONE); | ||||
| 
 | ||||
| 	i915_gem_object_init_memory_region(obj, mem); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| @ -26,9 +26,4 @@ i915_gem_object_create_lmem(struct drm_i915_private *i915, | ||||
| 			    resource_size_t size, | ||||
| 			    unsigned int flags); | ||||
| 
 | ||||
| int __i915_gem_lmem_object_init(struct intel_memory_region *mem, | ||||
| 				struct drm_i915_gem_object *obj, | ||||
| 				resource_size_t size, | ||||
| 				unsigned int flags); | ||||
| 
 | ||||
| #endif /* !__I915_GEM_LMEM_H */ | ||||
|  | ||||
| @ -19,6 +19,7 @@ | ||||
| #include "i915_gem_mman.h" | ||||
| #include "i915_trace.h" | ||||
| #include "i915_user_extensions.h" | ||||
| #include "i915_gem_ttm.h" | ||||
| #include "i915_vma.h" | ||||
| 
 | ||||
| static inline bool | ||||
| @ -624,6 +625,8 @@ mmap_offset_attach(struct drm_i915_gem_object *obj, | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	int err; | ||||
| 
 | ||||
| 	GEM_BUG_ON(obj->ops->mmap_offset || obj->ops->mmap_ops); | ||||
| 
 | ||||
| 	mmo = lookup_mmo(obj, mmap_type); | ||||
| 	if (mmo) | ||||
| 		goto out; | ||||
| @ -666,40 +669,47 @@ err: | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| __assign_mmap_offset(struct drm_file *file, | ||||
| 		     u32 handle, | ||||
| __assign_mmap_offset(struct drm_i915_gem_object *obj, | ||||
| 		     enum i915_mmap_type mmap_type, | ||||
| 		     u64 *offset) | ||||
| 		     u64 *offset, struct drm_file *file) | ||||
| { | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 
 | ||||
| 	if (i915_gem_object_never_mmap(obj)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	if (obj->ops->mmap_offset)  { | ||||
| 		*offset = obj->ops->mmap_offset(obj); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	if (mmap_type != I915_MMAP_TYPE_GTT && | ||||
| 	    !i915_gem_object_has_struct_page(obj) && | ||||
| 	    !i915_gem_object_type_has(obj, I915_GEM_OBJECT_HAS_IOMEM)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, mmap_type, file); | ||||
| 	if (IS_ERR(mmo)) | ||||
| 		return PTR_ERR(mmo); | ||||
| 
 | ||||
| 	*offset = drm_vma_node_offset_addr(&mmo->vma_node); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| __assign_mmap_offset_handle(struct drm_file *file, | ||||
| 			    u32 handle, | ||||
| 			    enum i915_mmap_type mmap_type, | ||||
| 			    u64 *offset) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj; | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	int err; | ||||
| 
 | ||||
| 	obj = i915_gem_object_lookup(file, handle); | ||||
| 	if (!obj) | ||||
| 		return -ENOENT; | ||||
| 
 | ||||
| 	if (i915_gem_object_never_mmap(obj)) { | ||||
| 		err = -ENODEV; | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	if (mmap_type != I915_MMAP_TYPE_GTT && | ||||
| 	    !i915_gem_object_has_struct_page(obj) && | ||||
| 	    !i915_gem_object_type_has(obj, I915_GEM_OBJECT_HAS_IOMEM)) { | ||||
| 		err = -ENODEV; | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, mmap_type, file); | ||||
| 	if (IS_ERR(mmo)) { | ||||
| 		err = PTR_ERR(mmo); | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	*offset = drm_vma_node_offset_addr(&mmo->vma_node); | ||||
| 	err = 0; | ||||
| out: | ||||
| 	err = __assign_mmap_offset(obj, mmap_type, offset, file); | ||||
| 	i915_gem_object_put(obj); | ||||
| 	return err; | ||||
| } | ||||
| @ -719,7 +729,7 @@ i915_gem_dumb_mmap_offset(struct drm_file *file, | ||||
| 	else | ||||
| 		mmap_type = I915_MMAP_TYPE_GTT; | ||||
| 
 | ||||
| 	return __assign_mmap_offset(file, handle, mmap_type, offset); | ||||
| 	return __assign_mmap_offset_handle(file, handle, mmap_type, offset); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
| @ -787,7 +797,7 @@ i915_gem_mmap_offset_ioctl(struct drm_device *dev, void *data, | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	return __assign_mmap_offset(file, args->handle, type, &args->offset); | ||||
| 	return __assign_mmap_offset_handle(file, args->handle, type, &args->offset); | ||||
| } | ||||
| 
 | ||||
| static void vm_open(struct vm_area_struct *vma) | ||||
| @ -891,8 +901,18 @@ int i915_gem_mmap(struct file *filp, struct vm_area_struct *vma) | ||||
| 		 * destroyed and will be invalid when the vma manager lock | ||||
| 		 * is released. | ||||
| 		 */ | ||||
| 		mmo = container_of(node, struct i915_mmap_offset, vma_node); | ||||
| 		obj = i915_gem_object_get_rcu(mmo->obj); | ||||
| 		if (!node->driver_private) { | ||||
| 			mmo = container_of(node, struct i915_mmap_offset, vma_node); | ||||
| 			obj = i915_gem_object_get_rcu(mmo->obj); | ||||
| 
 | ||||
| 			GEM_BUG_ON(obj && obj->ops->mmap_ops); | ||||
| 		} else { | ||||
| 			obj = i915_gem_object_get_rcu | ||||
| 				(container_of(node, struct drm_i915_gem_object, | ||||
| 					      base.vma_node)); | ||||
| 
 | ||||
| 			GEM_BUG_ON(obj && !obj->ops->mmap_ops); | ||||
| 		} | ||||
| 	} | ||||
| 	drm_vma_offset_unlock_lookup(dev->vma_offset_manager); | ||||
| 	rcu_read_unlock(); | ||||
| @ -914,7 +934,9 @@ int i915_gem_mmap(struct file *filp, struct vm_area_struct *vma) | ||||
| 	} | ||||
| 
 | ||||
| 	vma->vm_flags |= VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; | ||||
| 	vma->vm_private_data = mmo; | ||||
| 
 | ||||
| 	if (i915_gem_object_has_iomem(obj)) | ||||
| 		vma->vm_flags |= VM_IO; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We keep the ref on mmo->obj, not vm_file, but we require | ||||
| @ -928,6 +950,15 @@ int i915_gem_mmap(struct file *filp, struct vm_area_struct *vma) | ||||
| 	/* Drop the initial creation reference, the vma is now holding one. */ | ||||
| 	fput(anon); | ||||
| 
 | ||||
| 	if (obj->ops->mmap_ops) { | ||||
| 		vma->vm_page_prot = pgprot_decrypted(vm_get_page_prot(vma->vm_flags)); | ||||
| 		vma->vm_ops = obj->ops->mmap_ops; | ||||
| 		vma->vm_private_data = node->driver_private; | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	vma->vm_private_data = mmo; | ||||
| 
 | ||||
| 	switch (mmo->mmap_type) { | ||||
| 	case I915_MMAP_TYPE_WC: | ||||
| 		vma->vm_page_prot = | ||||
|  | ||||
| @ -172,7 +172,7 @@ static void i915_gem_close_object(struct drm_gem_object *gem, struct drm_file *f | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void __i915_gem_free_object_rcu(struct rcu_head *head) | ||||
| void __i915_gem_free_object_rcu(struct rcu_head *head) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = | ||||
| 		container_of(head, typeof(*obj), rcu); | ||||
| @ -208,59 +208,69 @@ static void __i915_gem_object_free_mmaps(struct drm_i915_gem_object *obj) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void __i915_gem_free_object(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	trace_i915_gem_object_destroy(obj); | ||||
| 
 | ||||
| 	if (!list_empty(&obj->vma.list)) { | ||||
| 		struct i915_vma *vma; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Note that the vma keeps an object reference while | ||||
| 		 * it is active, so it *should* not sleep while we | ||||
| 		 * destroy it. Our debug code errs insits it *might*. | ||||
| 		 * For the moment, play along. | ||||
| 		 */ | ||||
| 		spin_lock(&obj->vma.lock); | ||||
| 		while ((vma = list_first_entry_or_null(&obj->vma.list, | ||||
| 						       struct i915_vma, | ||||
| 						       obj_link))) { | ||||
| 			GEM_BUG_ON(vma->obj != obj); | ||||
| 			spin_unlock(&obj->vma.lock); | ||||
| 
 | ||||
| 			__i915_vma_put(vma); | ||||
| 
 | ||||
| 			spin_lock(&obj->vma.lock); | ||||
| 		} | ||||
| 		spin_unlock(&obj->vma.lock); | ||||
| 	} | ||||
| 
 | ||||
| 	__i915_gem_object_free_mmaps(obj); | ||||
| 
 | ||||
| 	GEM_BUG_ON(!list_empty(&obj->lut_list)); | ||||
| 
 | ||||
| 	atomic_set(&obj->mm.pages_pin_count, 0); | ||||
| 	__i915_gem_object_put_pages(obj); | ||||
| 	GEM_BUG_ON(i915_gem_object_has_pages(obj)); | ||||
| 	bitmap_free(obj->bit_17); | ||||
| 
 | ||||
| 	if (obj->base.import_attach) | ||||
| 		drm_prime_gem_destroy(&obj->base, NULL); | ||||
| 
 | ||||
| 	drm_gem_free_mmap_offset(&obj->base); | ||||
| 
 | ||||
| 	if (obj->ops->release) | ||||
| 		obj->ops->release(obj); | ||||
| 
 | ||||
| 	if (obj->mm.n_placements > 1) | ||||
| 		kfree(obj->mm.placements); | ||||
| 
 | ||||
| 	if (obj->shares_resv_from) | ||||
| 		i915_vm_resv_put(obj->shares_resv_from); | ||||
| } | ||||
| 
 | ||||
| static void __i915_gem_free_objects(struct drm_i915_private *i915, | ||||
| 				    struct llist_node *freed) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj, *on; | ||||
| 
 | ||||
| 	llist_for_each_entry_safe(obj, on, freed, freed) { | ||||
| 		trace_i915_gem_object_destroy(obj); | ||||
| 
 | ||||
| 		if (!list_empty(&obj->vma.list)) { | ||||
| 			struct i915_vma *vma; | ||||
| 
 | ||||
| 			/*
 | ||||
| 			 * Note that the vma keeps an object reference while | ||||
| 			 * it is active, so it *should* not sleep while we | ||||
| 			 * destroy it. Our debug code errs insits it *might*. | ||||
| 			 * For the moment, play along. | ||||
| 			 */ | ||||
| 			spin_lock(&obj->vma.lock); | ||||
| 			while ((vma = list_first_entry_or_null(&obj->vma.list, | ||||
| 							       struct i915_vma, | ||||
| 							       obj_link))) { | ||||
| 				GEM_BUG_ON(vma->obj != obj); | ||||
| 				spin_unlock(&obj->vma.lock); | ||||
| 
 | ||||
| 				__i915_vma_put(vma); | ||||
| 
 | ||||
| 				spin_lock(&obj->vma.lock); | ||||
| 			} | ||||
| 			spin_unlock(&obj->vma.lock); | ||||
| 		might_sleep(); | ||||
| 		if (obj->ops->delayed_free) { | ||||
| 			obj->ops->delayed_free(obj); | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		__i915_gem_object_free_mmaps(obj); | ||||
| 
 | ||||
| 		GEM_BUG_ON(!list_empty(&obj->lut_list)); | ||||
| 
 | ||||
| 		atomic_set(&obj->mm.pages_pin_count, 0); | ||||
| 		__i915_gem_object_put_pages(obj); | ||||
| 		GEM_BUG_ON(i915_gem_object_has_pages(obj)); | ||||
| 		bitmap_free(obj->bit_17); | ||||
| 
 | ||||
| 		if (obj->base.import_attach) | ||||
| 			drm_prime_gem_destroy(&obj->base, NULL); | ||||
| 
 | ||||
| 		drm_gem_free_mmap_offset(&obj->base); | ||||
| 
 | ||||
| 		if (obj->ops->release) | ||||
| 			obj->ops->release(obj); | ||||
| 
 | ||||
| 		if (obj->mm.n_placements > 1) | ||||
| 			kfree(obj->mm.placements); | ||||
| 
 | ||||
| 		if (obj->shares_resv_from) | ||||
| 			i915_vm_resv_put(obj->shares_resv_from); | ||||
| 		__i915_gem_free_object(obj); | ||||
| 
 | ||||
| 		/* But keep the pointer alive for RCU-protected lookups */ | ||||
| 		call_rcu(&obj->rcu, __i915_gem_free_object_rcu); | ||||
| @ -318,6 +328,7 @@ static void i915_gem_free_object(struct drm_gem_object *gem_obj) | ||||
| 	 * worker and performing frees directly from subsequent allocations for | ||||
| 	 * crude but effective memory throttling. | ||||
| 	 */ | ||||
| 
 | ||||
| 	if (llist_add(&obj->freed, &i915->mm.free_list)) | ||||
| 		queue_work(i915->wq, &i915->mm.free_work); | ||||
| } | ||||
| @ -410,6 +421,60 @@ int i915_gem_object_read_from_page(struct drm_i915_gem_object *obj, u64 offset, | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_gem_object_evictable - Whether object is likely evictable after unbind. | ||||
|  * @obj: The object to check | ||||
|  * | ||||
|  * This function checks whether the object is likely unvictable after unbind. | ||||
|  * If the object is not locked when checking, the result is only advisory. | ||||
|  * If the object is locked when checking, and the function returns true, | ||||
|  * then an eviction should indeed be possible. But since unlocked vma | ||||
|  * unpinning and unbinding is currently possible, the object can actually | ||||
|  * become evictable even if this function returns false. | ||||
|  * | ||||
|  * Return: true if the object may be evictable. False otherwise. | ||||
|  */ | ||||
| bool i915_gem_object_evictable(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct i915_vma *vma; | ||||
| 	int pin_count = atomic_read(&obj->mm.pages_pin_count); | ||||
| 
 | ||||
| 	if (!pin_count) | ||||
| 		return true; | ||||
| 
 | ||||
| 	spin_lock(&obj->vma.lock); | ||||
| 	list_for_each_entry(vma, &obj->vma.list, obj_link) { | ||||
| 		if (i915_vma_is_pinned(vma)) { | ||||
| 			spin_unlock(&obj->vma.lock); | ||||
| 			return false; | ||||
| 		} | ||||
| 		if (atomic_read(&vma->pages_count)) | ||||
| 			pin_count--; | ||||
| 	} | ||||
| 	spin_unlock(&obj->vma.lock); | ||||
| 	GEM_WARN_ON(pin_count < 0); | ||||
| 
 | ||||
| 	return pin_count == 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_gem_object_migratable - Whether the object is migratable out of the | ||||
|  * current region. | ||||
|  * @obj: Pointer to the object. | ||||
|  * | ||||
|  * Return: Whether the object is allowed to be resident in other | ||||
|  * regions than the current while pages are present. | ||||
|  */ | ||||
| bool i915_gem_object_migratable(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct intel_memory_region *mr = READ_ONCE(obj->mm.region); | ||||
| 
 | ||||
| 	if (!mr) | ||||
| 		return false; | ||||
| 
 | ||||
| 	return obj->mm.n_placements > 1; | ||||
| } | ||||
| 
 | ||||
| void i915_gem_init__objects(struct drm_i915_private *i915) | ||||
| { | ||||
| 	INIT_WORK(&i915->mm.free_work, __i915_gem_free_work); | ||||
|  | ||||
| @ -200,6 +200,9 @@ static inline bool i915_gem_object_trylock(struct drm_i915_gem_object *obj) | ||||
| 
 | ||||
| static inline void i915_gem_object_unlock(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	if (obj->ops->adjust_lru) | ||||
| 		obj->ops->adjust_lru(obj); | ||||
| 
 | ||||
| 	dma_resv_unlock(obj->base.resv); | ||||
| } | ||||
| 
 | ||||
| @ -339,14 +342,14 @@ struct scatterlist * | ||||
| __i915_gem_object_get_sg(struct drm_i915_gem_object *obj, | ||||
| 			 struct i915_gem_object_page_iter *iter, | ||||
| 			 unsigned int n, | ||||
| 			 unsigned int *offset, bool allow_alloc); | ||||
| 			 unsigned int *offset, bool allow_alloc, bool dma); | ||||
| 
 | ||||
| static inline struct scatterlist * | ||||
| i915_gem_object_get_sg(struct drm_i915_gem_object *obj, | ||||
| 		       unsigned int n, | ||||
| 		       unsigned int *offset, bool allow_alloc) | ||||
| { | ||||
| 	return __i915_gem_object_get_sg(obj, &obj->mm.get_page, n, offset, allow_alloc); | ||||
| 	return __i915_gem_object_get_sg(obj, &obj->mm.get_page, n, offset, allow_alloc, false); | ||||
| } | ||||
| 
 | ||||
| static inline struct scatterlist * | ||||
| @ -354,7 +357,7 @@ i915_gem_object_get_sg_dma(struct drm_i915_gem_object *obj, | ||||
| 			   unsigned int n, | ||||
| 			   unsigned int *offset, bool allow_alloc) | ||||
| { | ||||
| 	return __i915_gem_object_get_sg(obj, &obj->mm.get_dma_page, n, offset, allow_alloc); | ||||
| 	return __i915_gem_object_get_sg(obj, &obj->mm.get_dma_page, n, offset, allow_alloc, true); | ||||
| } | ||||
| 
 | ||||
| struct page * | ||||
| @ -587,6 +590,16 @@ int i915_gem_object_read_from_page(struct drm_i915_gem_object *obj, u64 offset, | ||||
| 
 | ||||
| bool i915_gem_object_is_shmem(const struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| void __i915_gem_free_object_rcu(struct rcu_head *head); | ||||
| 
 | ||||
| void __i915_gem_free_object(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| bool i915_gem_object_evictable(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| bool i915_gem_object_migratable(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| bool i915_gem_object_validates_to_lmem(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| #ifdef CONFIG_MMU_NOTIFIER | ||||
| static inline bool | ||||
| i915_gem_object_is_userptr(struct drm_i915_gem_object *obj) | ||||
|  | ||||
| @ -61,10 +61,26 @@ struct drm_i915_gem_object_ops { | ||||
| 		     const struct drm_i915_gem_pread *arg); | ||||
| 	int (*pwrite)(struct drm_i915_gem_object *obj, | ||||
| 		      const struct drm_i915_gem_pwrite *arg); | ||||
| 	u64 (*mmap_offset)(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| 	int (*dmabuf_export)(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * adjust_lru - notify that the madvise value was updated | ||||
| 	 * @obj: The gem object | ||||
| 	 * | ||||
| 	 * The madvise value may have been updated, or object was recently | ||||
| 	 * referenced so act accordingly (Perhaps changing an LRU list etc). | ||||
| 	 */ | ||||
| 	void (*adjust_lru)(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * delayed_free - Override the default delayed free implementation | ||||
| 	 */ | ||||
| 	void (*delayed_free)(struct drm_i915_gem_object *obj); | ||||
| 	void (*release)(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| 	const struct vm_operations_struct *mmap_ops; | ||||
| 	const char *name; /* friendly name for debug, e.g. lockdep classes */ | ||||
| }; | ||||
| 
 | ||||
| @ -187,12 +203,14 @@ struct drm_i915_gem_object { | ||||
| #define I915_BO_ALLOC_VOLATILE   BIT(1) | ||||
| #define I915_BO_ALLOC_STRUCT_PAGE BIT(2) | ||||
| #define I915_BO_ALLOC_CPU_CLEAR  BIT(3) | ||||
| #define I915_BO_ALLOC_USER       BIT(4) | ||||
| #define I915_BO_ALLOC_FLAGS (I915_BO_ALLOC_CONTIGUOUS | \ | ||||
| 			     I915_BO_ALLOC_VOLATILE | \ | ||||
| 			     I915_BO_ALLOC_STRUCT_PAGE | \ | ||||
| 			     I915_BO_ALLOC_CPU_CLEAR) | ||||
| #define I915_BO_READONLY         BIT(4) | ||||
| #define I915_TILING_QUIRK_BIT    5 /* unknown swizzling; do not release! */ | ||||
| 			     I915_BO_ALLOC_CPU_CLEAR | \ | ||||
| 			     I915_BO_ALLOC_USER) | ||||
| #define I915_BO_READONLY         BIT(5) | ||||
| #define I915_TILING_QUIRK_BIT    6 /* unknown swizzling; do not release! */ | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Is the object to be mapped as read-only to the GPU | ||||
| @ -310,6 +328,12 @@ struct drm_i915_gem_object { | ||||
| 		bool dirty:1; | ||||
| 	} mm; | ||||
| 
 | ||||
| 	struct { | ||||
| 		struct sg_table *cached_io_st; | ||||
| 		struct i915_gem_object_page_iter get_io_page; | ||||
| 		bool created:1; | ||||
| 	} ttm; | ||||
| 
 | ||||
| 	/** Record of address bit 17 of each page at last unbind. */ | ||||
| 	unsigned long *bit_17; | ||||
| 
 | ||||
|  | ||||
| @ -467,9 +467,8 @@ __i915_gem_object_get_sg(struct drm_i915_gem_object *obj, | ||||
| 			 struct i915_gem_object_page_iter *iter, | ||||
| 			 unsigned int n, | ||||
| 			 unsigned int *offset, | ||||
| 			 bool allow_alloc) | ||||
| 			 bool allow_alloc, bool dma) | ||||
| { | ||||
| 	const bool dma = iter == &obj->mm.get_dma_page; | ||||
| 	struct scatterlist *sg; | ||||
| 	unsigned int idx, count; | ||||
| 
 | ||||
|  | ||||
| @ -18,11 +18,7 @@ void i915_gem_object_init_memory_region(struct drm_i915_gem_object *obj, | ||||
| 
 | ||||
| 	mutex_lock(&mem->objects.lock); | ||||
| 
 | ||||
| 	if (obj->flags & I915_BO_ALLOC_VOLATILE) | ||||
| 		list_add(&obj->mm.region_link, &mem->objects.purgeable); | ||||
| 	else | ||||
| 		list_add(&obj->mm.region_link, &mem->objects.list); | ||||
| 
 | ||||
| 	list_add(&obj->mm.region_link, &mem->objects.list); | ||||
| 	mutex_unlock(&mem->objects.lock); | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										647
									
								
								drivers/gpu/drm/i915/gem/i915_gem_ttm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										647
									
								
								drivers/gpu/drm/i915/gem/i915_gem_ttm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,647 @@ | ||||
| // SPDX-License-Identifier: MIT
 | ||||
| /*
 | ||||
|  * Copyright © 2021 Intel Corporation | ||||
|  */ | ||||
| 
 | ||||
| #include <drm/ttm/ttm_bo_driver.h> | ||||
| #include <drm/ttm/ttm_placement.h> | ||||
| 
 | ||||
| #include "i915_drv.h" | ||||
| #include "intel_memory_region.h" | ||||
| #include "intel_region_ttm.h" | ||||
| 
 | ||||
| #include "gem/i915_gem_object.h" | ||||
| #include "gem/i915_gem_region.h" | ||||
| #include "gem/i915_gem_ttm.h" | ||||
| #include "gem/i915_gem_mman.h" | ||||
| 
 | ||||
| #define I915_PL_LMEM0 TTM_PL_PRIV | ||||
| #define I915_PL_SYSTEM TTM_PL_SYSTEM | ||||
| #define I915_PL_STOLEN TTM_PL_VRAM | ||||
| #define I915_PL_GGTT TTM_PL_TT | ||||
| 
 | ||||
| #define I915_TTM_PRIO_PURGE     0 | ||||
| #define I915_TTM_PRIO_NO_PAGES  1 | ||||
| #define I915_TTM_PRIO_HAS_PAGES 2 | ||||
| 
 | ||||
| /**
 | ||||
|  * struct i915_ttm_tt - TTM page vector with additional private information | ||||
|  * @ttm: The base TTM page vector. | ||||
|  * @dev: The struct device used for dma mapping and unmapping. | ||||
|  * @cached_st: The cached scatter-gather table. | ||||
|  * | ||||
|  * Note that DMA may be going on right up to the point where the page- | ||||
|  * vector is unpopulated in delayed destroy. Hence keep the | ||||
|  * scatter-gather table mapped and cached up to that point. This is | ||||
|  * different from the cached gem object io scatter-gather table which | ||||
|  * doesn't have an associated dma mapping. | ||||
|  */ | ||||
| struct i915_ttm_tt { | ||||
| 	struct ttm_tt ttm; | ||||
| 	struct device *dev; | ||||
| 	struct sg_table *cached_st; | ||||
| }; | ||||
| 
 | ||||
| static const struct ttm_place lmem0_sys_placement_flags[] = { | ||||
| 	{ | ||||
| 		.fpfn = 0, | ||||
| 		.lpfn = 0, | ||||
| 		.mem_type = I915_PL_LMEM0, | ||||
| 		.flags = 0, | ||||
| 	}, { | ||||
| 		.fpfn = 0, | ||||
| 		.lpfn = 0, | ||||
| 		.mem_type = I915_PL_SYSTEM, | ||||
| 		.flags = 0, | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| static struct ttm_placement i915_lmem0_placement = { | ||||
| 	.num_placement = 1, | ||||
| 	.placement = &lmem0_sys_placement_flags[0], | ||||
| 	.num_busy_placement = 1, | ||||
| 	.busy_placement = &lmem0_sys_placement_flags[0], | ||||
| }; | ||||
| 
 | ||||
| static struct ttm_placement i915_sys_placement = { | ||||
| 	.num_placement = 1, | ||||
| 	.placement = &lmem0_sys_placement_flags[1], | ||||
| 	.num_busy_placement = 1, | ||||
| 	.busy_placement = &lmem0_sys_placement_flags[1], | ||||
| }; | ||||
| 
 | ||||
| static void i915_ttm_adjust_lru(struct drm_i915_gem_object *obj); | ||||
| 
 | ||||
| static struct ttm_tt *i915_ttm_tt_create(struct ttm_buffer_object *bo, | ||||
| 					 uint32_t page_flags) | ||||
| { | ||||
| 	struct ttm_resource_manager *man = | ||||
| 		ttm_manager_type(bo->bdev, bo->resource->mem_type); | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 	struct i915_ttm_tt *i915_tt; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	i915_tt = kzalloc(sizeof(*i915_tt), GFP_KERNEL); | ||||
| 	if (!i915_tt) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	if (obj->flags & I915_BO_ALLOC_CPU_CLEAR && | ||||
| 	    man->use_tt) | ||||
| 		page_flags |= TTM_PAGE_FLAG_ZERO_ALLOC; | ||||
| 
 | ||||
| 	ret = ttm_tt_init(&i915_tt->ttm, bo, page_flags, ttm_write_combined); | ||||
| 	if (ret) { | ||||
| 		kfree(i915_tt); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	i915_tt->dev = obj->base.dev->dev; | ||||
| 
 | ||||
| 	return &i915_tt->ttm; | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_tt_unpopulate(struct ttm_device *bdev, struct ttm_tt *ttm) | ||||
| { | ||||
| 	struct i915_ttm_tt *i915_tt = container_of(ttm, typeof(*i915_tt), ttm); | ||||
| 
 | ||||
| 	if (i915_tt->cached_st) { | ||||
| 		dma_unmap_sgtable(i915_tt->dev, i915_tt->cached_st, | ||||
| 				  DMA_BIDIRECTIONAL, 0); | ||||
| 		sg_free_table(i915_tt->cached_st); | ||||
| 		kfree(i915_tt->cached_st); | ||||
| 		i915_tt->cached_st = NULL; | ||||
| 	} | ||||
| 	ttm_pool_free(&bdev->pool, ttm); | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_tt_destroy(struct ttm_device *bdev, struct ttm_tt *ttm) | ||||
| { | ||||
| 	struct i915_ttm_tt *i915_tt = container_of(ttm, typeof(*i915_tt), ttm); | ||||
| 
 | ||||
| 	ttm_tt_destroy_common(bdev, ttm); | ||||
| 	kfree(i915_tt); | ||||
| } | ||||
| 
 | ||||
| static bool i915_ttm_eviction_valuable(struct ttm_buffer_object *bo, | ||||
| 				       const struct ttm_place *place) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 
 | ||||
| 	/* Will do for now. Our pinned objects are still on TTM's LRU lists */ | ||||
| 	if (!i915_gem_object_evictable(obj)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/* This isn't valid with a buddy allocator */ | ||||
| 	return ttm_bo_eviction_valuable(bo, place); | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_evict_flags(struct ttm_buffer_object *bo, | ||||
| 				 struct ttm_placement *placement) | ||||
| { | ||||
| 	*placement = i915_sys_placement; | ||||
| } | ||||
| 
 | ||||
| static int i915_ttm_move_notify(struct ttm_buffer_object *bo) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = i915_gem_object_unbind(obj, I915_GEM_OBJECT_UNBIND_ACTIVE); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = __i915_gem_object_put_pages(obj); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_free_cached_io_st(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct radix_tree_iter iter; | ||||
| 	void __rcu **slot; | ||||
| 
 | ||||
| 	if (!obj->ttm.cached_io_st) | ||||
| 		return; | ||||
| 
 | ||||
| 	rcu_read_lock(); | ||||
| 	radix_tree_for_each_slot(slot, &obj->ttm.get_io_page.radix, &iter, 0) | ||||
| 		radix_tree_delete(&obj->ttm.get_io_page.radix, iter.index); | ||||
| 	rcu_read_unlock(); | ||||
| 
 | ||||
| 	sg_free_table(obj->ttm.cached_io_st); | ||||
| 	kfree(obj->ttm.cached_io_st); | ||||
| 	obj->ttm.cached_io_st = NULL; | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_purge(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); | ||||
| 	struct ttm_operation_ctx ctx = { | ||||
| 		.interruptible = true, | ||||
| 		.no_wait_gpu = false, | ||||
| 	}; | ||||
| 	struct ttm_placement place = {}; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (obj->mm.madv == __I915_MADV_PURGED) | ||||
| 		return; | ||||
| 
 | ||||
| 	/* TTM's purge interface. Note that we might be reentering. */ | ||||
| 	ret = ttm_bo_validate(bo, &place, &ctx); | ||||
| 
 | ||||
| 	if (!ret) { | ||||
| 		i915_ttm_free_cached_io_st(obj); | ||||
| 		obj->mm.madv = __I915_MADV_PURGED; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_swap_notify(struct ttm_buffer_object *bo) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 	int ret = i915_ttm_move_notify(bo); | ||||
| 
 | ||||
| 	GEM_WARN_ON(ret); | ||||
| 	GEM_WARN_ON(obj->ttm.cached_io_st); | ||||
| 	if (!ret && obj->mm.madv != I915_MADV_WILLNEED) | ||||
| 		i915_ttm_purge(obj); | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_delete_mem_notify(struct ttm_buffer_object *bo) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 
 | ||||
| 	if (likely(obj)) { | ||||
| 		/* This releases all gem object bindings to the backend. */ | ||||
| 		__i915_gem_free_object(obj); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static struct intel_memory_region * | ||||
| i915_ttm_region(struct ttm_device *bdev, int ttm_mem_type) | ||||
| { | ||||
| 	struct drm_i915_private *i915 = container_of(bdev, typeof(*i915), bdev); | ||||
| 
 | ||||
| 	/* There's some room for optimization here... */ | ||||
| 	GEM_BUG_ON(ttm_mem_type != I915_PL_SYSTEM && | ||||
| 		   ttm_mem_type < I915_PL_LMEM0); | ||||
| 	if (ttm_mem_type == I915_PL_SYSTEM) | ||||
| 		return intel_memory_region_lookup(i915, INTEL_MEMORY_SYSTEM, | ||||
| 						  0); | ||||
| 
 | ||||
| 	return intel_memory_region_lookup(i915, INTEL_MEMORY_LOCAL, | ||||
| 					  ttm_mem_type - I915_PL_LMEM0); | ||||
| } | ||||
| 
 | ||||
| static struct sg_table *i915_ttm_tt_get_st(struct ttm_tt *ttm) | ||||
| { | ||||
| 	struct i915_ttm_tt *i915_tt = container_of(ttm, typeof(*i915_tt), ttm); | ||||
| 	struct scatterlist *sg; | ||||
| 	struct sg_table *st; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (i915_tt->cached_st) | ||||
| 		return i915_tt->cached_st; | ||||
| 
 | ||||
| 	st = kzalloc(sizeof(*st), GFP_KERNEL); | ||||
| 	if (!st) | ||||
| 		return ERR_PTR(-ENOMEM); | ||||
| 
 | ||||
| 	sg = __sg_alloc_table_from_pages | ||||
| 		(st, ttm->pages, ttm->num_pages, 0, | ||||
| 		 (unsigned long)ttm->num_pages << PAGE_SHIFT, | ||||
| 		 i915_sg_segment_size(), NULL, 0, GFP_KERNEL); | ||||
| 	if (IS_ERR(sg)) { | ||||
| 		kfree(st); | ||||
| 		return ERR_CAST(sg); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = dma_map_sgtable(i915_tt->dev, st, DMA_BIDIRECTIONAL, 0); | ||||
| 	if (ret) { | ||||
| 		sg_free_table(st); | ||||
| 		kfree(st); | ||||
| 		return ERR_PTR(ret); | ||||
| 	} | ||||
| 
 | ||||
| 	i915_tt->cached_st = st; | ||||
| 	return st; | ||||
| } | ||||
| 
 | ||||
| static struct sg_table * | ||||
| i915_ttm_resource_get_st(struct drm_i915_gem_object *obj, | ||||
| 			 struct ttm_resource *res) | ||||
| { | ||||
| 	struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); | ||||
| 	struct ttm_resource_manager *man = | ||||
| 		ttm_manager_type(bo->bdev, res->mem_type); | ||||
| 
 | ||||
| 	if (man->use_tt) | ||||
| 		return i915_ttm_tt_get_st(bo->ttm); | ||||
| 
 | ||||
| 	return intel_region_ttm_node_to_st(obj->mm.region, res); | ||||
| } | ||||
| 
 | ||||
| static int i915_ttm_move(struct ttm_buffer_object *bo, bool evict, | ||||
| 			 struct ttm_operation_ctx *ctx, | ||||
| 			 struct ttm_resource *dst_mem, | ||||
| 			 struct ttm_place *hop) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 	struct ttm_resource_manager *dst_man = | ||||
| 		ttm_manager_type(bo->bdev, dst_mem->mem_type); | ||||
| 	struct ttm_resource_manager *src_man = | ||||
| 		ttm_manager_type(bo->bdev, bo->resource->mem_type); | ||||
| 	struct intel_memory_region *dst_reg, *src_reg; | ||||
| 	union { | ||||
| 		struct ttm_kmap_iter_tt tt; | ||||
| 		struct ttm_kmap_iter_iomap io; | ||||
| 	} _dst_iter, _src_iter; | ||||
| 	struct ttm_kmap_iter *dst_iter, *src_iter; | ||||
| 	struct sg_table *dst_st; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dst_reg = i915_ttm_region(bo->bdev, dst_mem->mem_type); | ||||
| 	src_reg = i915_ttm_region(bo->bdev, bo->resource->mem_type); | ||||
| 	GEM_BUG_ON(!dst_reg || !src_reg); | ||||
| 
 | ||||
| 	/* Sync for now. We could do the actual copy async. */ | ||||
| 	ret = ttm_bo_wait_ctx(bo, ctx); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = i915_ttm_move_notify(bo); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	if (obj->mm.madv != I915_MADV_WILLNEED) { | ||||
| 		i915_ttm_purge(obj); | ||||
| 		ttm_resource_free(bo, &dst_mem); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Populate ttm with pages if needed. Typically system memory. */ | ||||
| 	if (bo->ttm && (dst_man->use_tt || | ||||
| 			(bo->ttm->page_flags & TTM_PAGE_FLAG_SWAPPED))) { | ||||
| 		ret = ttm_tt_populate(bo->bdev, bo->ttm, ctx); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	dst_st = i915_ttm_resource_get_st(obj, dst_mem); | ||||
| 	if (IS_ERR(dst_st)) | ||||
| 		return PTR_ERR(dst_st); | ||||
| 
 | ||||
| 	/* If we start mapping GGTT, we can no longer use man::use_tt here. */ | ||||
| 	dst_iter = dst_man->use_tt ? | ||||
| 		ttm_kmap_iter_tt_init(&_dst_iter.tt, bo->ttm) : | ||||
| 		ttm_kmap_iter_iomap_init(&_dst_iter.io, &dst_reg->iomap, | ||||
| 					 dst_st, dst_reg->region.start); | ||||
| 
 | ||||
| 	src_iter = src_man->use_tt ? | ||||
| 		ttm_kmap_iter_tt_init(&_src_iter.tt, bo->ttm) : | ||||
| 		ttm_kmap_iter_iomap_init(&_src_iter.io, &src_reg->iomap, | ||||
| 					 obj->ttm.cached_io_st, | ||||
| 					 src_reg->region.start); | ||||
| 
 | ||||
| 	ttm_move_memcpy(bo, dst_mem->num_pages, dst_iter, src_iter); | ||||
| 	ttm_bo_move_sync_cleanup(bo, dst_mem); | ||||
| 	i915_ttm_free_cached_io_st(obj); | ||||
| 
 | ||||
| 	if (!dst_man->use_tt) { | ||||
| 		obj->ttm.cached_io_st = dst_st; | ||||
| 		obj->ttm.get_io_page.sg_pos = dst_st->sgl; | ||||
| 		obj->ttm.get_io_page.sg_idx = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int i915_ttm_io_mem_reserve(struct ttm_device *bdev, struct ttm_resource *mem) | ||||
| { | ||||
| 	if (mem->mem_type < I915_PL_LMEM0) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	mem->bus.caching = ttm_write_combined; | ||||
| 	mem->bus.is_iomem = true; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static unsigned long i915_ttm_io_mem_pfn(struct ttm_buffer_object *bo, | ||||
| 					 unsigned long page_offset) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 	unsigned long base = obj->mm.region->iomap.base - obj->mm.region->region.start; | ||||
| 	struct scatterlist *sg; | ||||
| 	unsigned int ofs; | ||||
| 
 | ||||
| 	GEM_WARN_ON(bo->ttm); | ||||
| 
 | ||||
| 	sg = __i915_gem_object_get_sg(obj, &obj->ttm.get_io_page, page_offset, &ofs, true, true); | ||||
| 
 | ||||
| 	return ((base + sg_dma_address(sg)) >> PAGE_SHIFT) + ofs; | ||||
| } | ||||
| 
 | ||||
| static struct ttm_device_funcs i915_ttm_bo_driver = { | ||||
| 	.ttm_tt_create = i915_ttm_tt_create, | ||||
| 	.ttm_tt_unpopulate = i915_ttm_tt_unpopulate, | ||||
| 	.ttm_tt_destroy = i915_ttm_tt_destroy, | ||||
| 	.eviction_valuable = i915_ttm_eviction_valuable, | ||||
| 	.evict_flags = i915_ttm_evict_flags, | ||||
| 	.move = i915_ttm_move, | ||||
| 	.swap_notify = i915_ttm_swap_notify, | ||||
| 	.delete_mem_notify = i915_ttm_delete_mem_notify, | ||||
| 	.io_mem_reserve = i915_ttm_io_mem_reserve, | ||||
| 	.io_mem_pfn = i915_ttm_io_mem_pfn, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_ttm_driver - Return a pointer to the TTM device funcs | ||||
|  * | ||||
|  * Return: Pointer to statically allocated TTM device funcs. | ||||
|  */ | ||||
| struct ttm_device_funcs *i915_ttm_driver(void) | ||||
| { | ||||
| 	return &i915_ttm_bo_driver; | ||||
| } | ||||
| 
 | ||||
| static int i915_ttm_get_pages(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); | ||||
| 	struct ttm_operation_ctx ctx = { | ||||
| 		.interruptible = true, | ||||
| 		.no_wait_gpu = false, | ||||
| 	}; | ||||
| 	struct sg_table *st; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Move to the requested placement. */ | ||||
| 	ret = ttm_bo_validate(bo, &i915_lmem0_placement, &ctx); | ||||
| 	if (ret) | ||||
| 		return ret == -ENOSPC ? -ENXIO : ret; | ||||
| 
 | ||||
| 	/* Object either has a page vector or is an iomem object */ | ||||
| 	st = bo->ttm ? i915_ttm_tt_get_st(bo->ttm) : obj->ttm.cached_io_st; | ||||
| 	if (IS_ERR(st)) | ||||
| 		return PTR_ERR(st); | ||||
| 
 | ||||
| 	__i915_gem_object_set_pages(obj, st, i915_sg_dma_sizes(st->sgl)); | ||||
| 
 | ||||
| 	i915_ttm_adjust_lru(obj); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_put_pages(struct drm_i915_gem_object *obj, | ||||
| 			       struct sg_table *st) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * We're currently not called from a shrinker, so put_pages() | ||||
| 	 * typically means the object is about to destroyed, or called | ||||
| 	 * from move_notify(). So just avoid doing much for now. | ||||
| 	 * If the object is not destroyed next, The TTM eviction logic | ||||
| 	 * and shrinkers will move it out if needed. | ||||
| 	 */ | ||||
| 
 | ||||
| 	i915_ttm_adjust_lru(obj); | ||||
| } | ||||
| 
 | ||||
| static void i915_ttm_adjust_lru(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Don't manipulate the TTM LRUs while in TTM bo destruction. | ||||
| 	 * We're called through i915_ttm_delete_mem_notify(). | ||||
| 	 */ | ||||
| 	if (!kref_read(&bo->kref)) | ||||
| 		return; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Put on the correct LRU list depending on the MADV status | ||||
| 	 */ | ||||
| 	spin_lock(&bo->bdev->lru_lock); | ||||
| 	if (obj->mm.madv != I915_MADV_WILLNEED) { | ||||
| 		bo->priority = I915_TTM_PRIO_PURGE; | ||||
| 	} else if (!i915_gem_object_has_pages(obj)) { | ||||
| 		if (bo->priority < I915_TTM_PRIO_HAS_PAGES) | ||||
| 			bo->priority = I915_TTM_PRIO_HAS_PAGES; | ||||
| 	} else { | ||||
| 		if (bo->priority > I915_TTM_PRIO_NO_PAGES) | ||||
| 			bo->priority = I915_TTM_PRIO_NO_PAGES; | ||||
| 	} | ||||
| 
 | ||||
| 	ttm_bo_move_to_lru_tail(bo, bo->resource, NULL); | ||||
| 	spin_unlock(&bo->bdev->lru_lock); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * TTM-backed gem object destruction requires some clarification. | ||||
|  * Basically we have two possibilities here. We can either rely on the | ||||
|  * i915 delayed destruction and put the TTM object when the object | ||||
|  * is idle. This would be detected by TTM which would bypass the | ||||
|  * TTM delayed destroy handling. The other approach is to put the TTM | ||||
|  * object early and rely on the TTM destroyed handling, and then free | ||||
|  * the leftover parts of the GEM object once TTM's destroyed list handling is | ||||
|  * complete. For now, we rely on the latter for two reasons: | ||||
|  * a) TTM can evict an object even when it's on the delayed destroy list, | ||||
|  * which in theory allows for complete eviction. | ||||
|  * b) There is work going on in TTM to allow freeing an object even when | ||||
|  * it's not idle, and using the TTM destroyed list handling could help us | ||||
|  * benefit from that. | ||||
|  */ | ||||
| static void i915_ttm_delayed_free(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	if (obj->ttm.created) { | ||||
| 		ttm_bo_put(i915_gem_to_ttm(obj)); | ||||
| 	} else { | ||||
| 		__i915_gem_free_object(obj); | ||||
| 		call_rcu(&obj->rcu, __i915_gem_free_object_rcu); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static vm_fault_t vm_fault_ttm(struct vm_fault *vmf) | ||||
| { | ||||
| 	struct vm_area_struct *area = vmf->vma; | ||||
| 	struct drm_i915_gem_object *obj = | ||||
| 		i915_ttm_to_gem(area->vm_private_data); | ||||
| 
 | ||||
| 	/* Sanity check that we allow writing into this object */ | ||||
| 	if (unlikely(i915_gem_object_is_readonly(obj) && | ||||
| 		     area->vm_flags & VM_WRITE)) | ||||
| 		return VM_FAULT_SIGBUS; | ||||
| 
 | ||||
| 	return ttm_bo_vm_fault(vmf); | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| vm_access_ttm(struct vm_area_struct *area, unsigned long addr, | ||||
| 	      void *buf, int len, int write) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = | ||||
| 		i915_ttm_to_gem(area->vm_private_data); | ||||
| 
 | ||||
| 	if (i915_gem_object_is_readonly(obj) && write) | ||||
| 		return -EACCES; | ||||
| 
 | ||||
| 	return ttm_bo_vm_access(area, addr, buf, len, write); | ||||
| } | ||||
| 
 | ||||
| static void ttm_vm_open(struct vm_area_struct *vma) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = | ||||
| 		i915_ttm_to_gem(vma->vm_private_data); | ||||
| 
 | ||||
| 	GEM_BUG_ON(!obj); | ||||
| 	i915_gem_object_get(obj); | ||||
| } | ||||
| 
 | ||||
| static void ttm_vm_close(struct vm_area_struct *vma) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = | ||||
| 		i915_ttm_to_gem(vma->vm_private_data); | ||||
| 
 | ||||
| 	GEM_BUG_ON(!obj); | ||||
| 	i915_gem_object_put(obj); | ||||
| } | ||||
| 
 | ||||
| static const struct vm_operations_struct vm_ops_ttm = { | ||||
| 	.fault = vm_fault_ttm, | ||||
| 	.access = vm_access_ttm, | ||||
| 	.open = ttm_vm_open, | ||||
| 	.close = ttm_vm_close, | ||||
| }; | ||||
| 
 | ||||
| static u64 i915_ttm_mmap_offset(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	/* The ttm_bo must be allocated with I915_BO_ALLOC_USER */ | ||||
| 	GEM_BUG_ON(!drm_mm_node_allocated(&obj->base.vma_node.vm_node)); | ||||
| 
 | ||||
| 	return drm_vma_node_offset_addr(&obj->base.vma_node); | ||||
| } | ||||
| 
 | ||||
| const struct drm_i915_gem_object_ops i915_gem_ttm_obj_ops = { | ||||
| 	.name = "i915_gem_object_ttm", | ||||
| 	.flags = I915_GEM_OBJECT_HAS_IOMEM, | ||||
| 
 | ||||
| 	.get_pages = i915_ttm_get_pages, | ||||
| 	.put_pages = i915_ttm_put_pages, | ||||
| 	.truncate = i915_ttm_purge, | ||||
| 	.adjust_lru = i915_ttm_adjust_lru, | ||||
| 	.delayed_free = i915_ttm_delayed_free, | ||||
| 	.mmap_offset = i915_ttm_mmap_offset, | ||||
| 	.mmap_ops = &vm_ops_ttm, | ||||
| }; | ||||
| 
 | ||||
| void i915_ttm_bo_destroy(struct ttm_buffer_object *bo) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); | ||||
| 
 | ||||
| 	i915_gem_object_release_memory_region(obj); | ||||
| 	mutex_destroy(&obj->ttm.get_io_page.lock); | ||||
| 	if (obj->ttm.created) | ||||
| 		call_rcu(&obj->rcu, __i915_gem_free_object_rcu); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * __i915_gem_ttm_object_init - Initialize a ttm-backed i915 gem object | ||||
|  * @mem: The initial memory region for the object. | ||||
|  * @obj: The gem object. | ||||
|  * @size: Object size in bytes. | ||||
|  * @flags: gem object flags. | ||||
|  * | ||||
|  * Return: 0 on success, negative error code on failure. | ||||
|  */ | ||||
| int __i915_gem_ttm_object_init(struct intel_memory_region *mem, | ||||
| 			       struct drm_i915_gem_object *obj, | ||||
| 			       resource_size_t size, | ||||
| 			       unsigned int flags) | ||||
| { | ||||
| 	static struct lock_class_key lock_class; | ||||
| 	struct drm_i915_private *i915 = mem->i915; | ||||
| 	enum ttm_bo_type bo_type; | ||||
| 	size_t alignment = 0; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Adjust alignment to GPU- and CPU huge page sizes. */ | ||||
| 
 | ||||
| 	if (mem->is_range_manager) { | ||||
| 		if (size >= SZ_1G) | ||||
| 			alignment = SZ_1G >> PAGE_SHIFT; | ||||
| 		else if (size >= SZ_2M) | ||||
| 			alignment = SZ_2M >> PAGE_SHIFT; | ||||
| 		else if (size >= SZ_64K) | ||||
| 			alignment = SZ_64K >> PAGE_SHIFT; | ||||
| 	} | ||||
| 
 | ||||
| 	drm_gem_private_object_init(&i915->drm, &obj->base, size); | ||||
| 	i915_gem_object_init(obj, &i915_gem_ttm_obj_ops, &lock_class, flags); | ||||
| 	i915_gem_object_init_memory_region(obj, mem); | ||||
| 	i915_gem_object_make_unshrinkable(obj); | ||||
| 	obj->read_domains = I915_GEM_DOMAIN_WC | I915_GEM_DOMAIN_GTT; | ||||
| 	i915_gem_object_set_cache_coherency(obj, I915_CACHE_NONE); | ||||
| 	INIT_RADIX_TREE(&obj->ttm.get_io_page.radix, GFP_KERNEL | __GFP_NOWARN); | ||||
| 	mutex_init(&obj->ttm.get_io_page.lock); | ||||
| 
 | ||||
| 	bo_type = (obj->flags & I915_BO_ALLOC_USER) ? ttm_bo_type_device : | ||||
| 		ttm_bo_type_kernel; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If this function fails, it will call the destructor, but | ||||
| 	 * our caller still owns the object. So no freeing in the | ||||
| 	 * destructor until obj->ttm.created is true. | ||||
| 	 * Similarly, in delayed_destroy, we can't call ttm_bo_put() | ||||
| 	 * until successful initialization. | ||||
| 	 */ | ||||
| 	obj->base.vma_node.driver_private = i915_gem_to_ttm(obj); | ||||
| 	ret = ttm_bo_init(&i915->bdev, i915_gem_to_ttm(obj), size, | ||||
| 			  bo_type, &i915_sys_placement, alignment, | ||||
| 			  true, NULL, NULL, i915_ttm_bo_destroy); | ||||
| 
 | ||||
| 	if (!ret) | ||||
| 		obj->ttm.created = true; | ||||
| 
 | ||||
| 	/* i915 wants -ENXIO when out of memory region space. */ | ||||
| 	return (ret == -ENOSPC) ? -ENXIO : ret; | ||||
| } | ||||
							
								
								
									
										48
									
								
								drivers/gpu/drm/i915/gem/i915_gem_ttm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								drivers/gpu/drm/i915/gem/i915_gem_ttm.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| /* SPDX-License-Identifier: MIT */ | ||||
| /*
 | ||||
|  * Copyright © 2021 Intel Corporation | ||||
|  */ | ||||
| #ifndef _I915_GEM_TTM_H_ | ||||
| #define _I915_GEM_TTM_H_ | ||||
| 
 | ||||
| #include "gem/i915_gem_object_types.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_gem_to_ttm - Convert a struct drm_i915_gem_object to a | ||||
|  * struct ttm_buffer_object. | ||||
|  * @obj: Pointer to the gem object. | ||||
|  * | ||||
|  * Return: Pointer to the embedded struct ttm_buffer_object. | ||||
|  */ | ||||
| static inline struct ttm_buffer_object * | ||||
| i915_gem_to_ttm(struct drm_i915_gem_object *obj) | ||||
| { | ||||
| 	return &obj->__do_not_access; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * i915 ttm gem object destructor. Internal use only. | ||||
|  */ | ||||
| void i915_ttm_bo_destroy(struct ttm_buffer_object *bo); | ||||
| 
 | ||||
| /**
 | ||||
|  * i915_ttm_to_gem - Convert a struct ttm_buffer_object to an embedding | ||||
|  * struct drm_i915_gem_object. | ||||
|  * | ||||
|  * Return: Pointer to the embedding struct ttm_buffer_object, or NULL | ||||
|  * if the object was not an i915 ttm object. | ||||
|  */ | ||||
| static inline struct drm_i915_gem_object * | ||||
| i915_ttm_to_gem(struct ttm_buffer_object *bo) | ||||
| { | ||||
| 	if (GEM_WARN_ON(bo->destroy != i915_ttm_bo_destroy)) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	return container_of(bo, struct drm_i915_gem_object, __do_not_access); | ||||
| } | ||||
| 
 | ||||
| int __i915_gem_ttm_object_init(struct intel_memory_region *mem, | ||||
| 			       struct drm_i915_gem_object *obj, | ||||
| 			       resource_size_t size, | ||||
| 			       unsigned int flags); | ||||
| #endif | ||||
| @ -578,16 +578,17 @@ static bool assert_mmap_offset(struct drm_i915_private *i915, | ||||
| 			       int expected) | ||||
| { | ||||
| 	struct drm_i915_gem_object *obj; | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	u64 offset; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	obj = i915_gem_object_create_internal(i915, size); | ||||
| 	if (IS_ERR(obj)) | ||||
| 		return false; | ||||
| 		return expected && expected == PTR_ERR(obj); | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, I915_MMAP_OFFSET_GTT, NULL); | ||||
| 	ret = __assign_mmap_offset(obj, I915_MMAP_TYPE_GTT, &offset, NULL); | ||||
| 	i915_gem_object_put(obj); | ||||
| 
 | ||||
| 	return PTR_ERR_OR_ZERO(mmo) == expected; | ||||
| 	return ret == expected; | ||||
| } | ||||
| 
 | ||||
| static void disable_retire_worker(struct drm_i915_private *i915) | ||||
| @ -622,8 +623,8 @@ static int igt_mmap_offset_exhaustion(void *arg) | ||||
| 	struct drm_mm *mm = &i915->drm.vma_offset_manager->vm_addr_space_mm; | ||||
| 	struct drm_i915_gem_object *obj; | ||||
| 	struct drm_mm_node *hole, *next; | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	int loop, err = 0; | ||||
| 	u64 offset; | ||||
| 
 | ||||
| 	/* Disable background reaper */ | ||||
| 	disable_retire_worker(i915); | ||||
| @ -684,13 +685,13 @@ static int igt_mmap_offset_exhaustion(void *arg) | ||||
| 	obj = i915_gem_object_create_internal(i915, PAGE_SIZE); | ||||
| 	if (IS_ERR(obj)) { | ||||
| 		err = PTR_ERR(obj); | ||||
| 		pr_err("Unable to create object for reclaimed hole\n"); | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, I915_MMAP_OFFSET_GTT, NULL); | ||||
| 	if (IS_ERR(mmo)) { | ||||
| 	err = __assign_mmap_offset(obj, I915_MMAP_TYPE_GTT, &offset, NULL); | ||||
| 	if (err) { | ||||
| 		pr_err("Unable to insert object into reclaimed hole\n"); | ||||
| 		err = PTR_ERR(mmo); | ||||
| 		goto err_obj; | ||||
| 	} | ||||
| 
 | ||||
| @ -865,10 +866,10 @@ static int __igt_mmap(struct drm_i915_private *i915, | ||||
| 		      struct drm_i915_gem_object *obj, | ||||
| 		      enum i915_mmap_type type) | ||||
| { | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	struct vm_area_struct *area; | ||||
| 	unsigned long addr; | ||||
| 	int err, i; | ||||
| 	u64 offset; | ||||
| 
 | ||||
| 	if (!can_mmap(obj, type)) | ||||
| 		return 0; | ||||
| @ -879,11 +880,11 @@ static int __igt_mmap(struct drm_i915_private *i915, | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, type, NULL); | ||||
| 	if (IS_ERR(mmo)) | ||||
| 		return PTR_ERR(mmo); | ||||
| 	err = __assign_mmap_offset(obj, type, &offset, NULL); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	addr = igt_mmap_node(i915, &mmo->vma_node, 0, PROT_WRITE, MAP_SHARED); | ||||
| 	addr = igt_mmap_offset(i915, offset, obj->base.size, PROT_WRITE, MAP_SHARED); | ||||
| 	if (IS_ERR_VALUE(addr)) | ||||
| 		return addr; | ||||
| 
 | ||||
| @ -897,13 +898,6 @@ static int __igt_mmap(struct drm_i915_private *i915, | ||||
| 		goto out_unmap; | ||||
| 	} | ||||
| 
 | ||||
| 	if (area->vm_private_data != mmo) { | ||||
| 		pr_err("%s: vm_area_struct did not point back to our mmap_offset object!\n", | ||||
| 		       obj->mm.region->name); | ||||
| 		err = -EINVAL; | ||||
| 		goto out_unmap; | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < obj->base.size / sizeof(u32); i++) { | ||||
| 		u32 __user *ux = u64_to_user_ptr((u64)(addr + i * sizeof(*ux))); | ||||
| 		u32 x; | ||||
| @ -961,7 +955,7 @@ static int igt_mmap(void *arg) | ||||
| 			struct drm_i915_gem_object *obj; | ||||
| 			int err; | ||||
| 
 | ||||
| 			obj = i915_gem_object_create_region(mr, sizes[i], 0); | ||||
| 			obj = i915_gem_object_create_region(mr, sizes[i], I915_BO_ALLOC_USER); | ||||
| 			if (obj == ERR_PTR(-ENODEV)) | ||||
| 				continue; | ||||
| 
 | ||||
| @ -1004,12 +998,12 @@ static int __igt_mmap_access(struct drm_i915_private *i915, | ||||
| 			     struct drm_i915_gem_object *obj, | ||||
| 			     enum i915_mmap_type type) | ||||
| { | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	unsigned long __user *ptr; | ||||
| 	unsigned long A, B; | ||||
| 	unsigned long x, y; | ||||
| 	unsigned long addr; | ||||
| 	int err; | ||||
| 	u64 offset; | ||||
| 
 | ||||
| 	memset(&A, 0xAA, sizeof(A)); | ||||
| 	memset(&B, 0xBB, sizeof(B)); | ||||
| @ -1017,11 +1011,11 @@ static int __igt_mmap_access(struct drm_i915_private *i915, | ||||
| 	if (!can_mmap(obj, type) || !can_access(obj)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, type, NULL); | ||||
| 	if (IS_ERR(mmo)) | ||||
| 		return PTR_ERR(mmo); | ||||
| 	err = __assign_mmap_offset(obj, type, &offset, NULL); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	addr = igt_mmap_node(i915, &mmo->vma_node, 0, PROT_WRITE, MAP_SHARED); | ||||
| 	addr = igt_mmap_offset(i915, offset, obj->base.size, PROT_WRITE, MAP_SHARED); | ||||
| 	if (IS_ERR_VALUE(addr)) | ||||
| 		return addr; | ||||
| 	ptr = (unsigned long __user *)addr; | ||||
| @ -1081,7 +1075,7 @@ static int igt_mmap_access(void *arg) | ||||
| 		struct drm_i915_gem_object *obj; | ||||
| 		int err; | ||||
| 
 | ||||
| 		obj = i915_gem_object_create_region(mr, PAGE_SIZE, 0); | ||||
| 		obj = i915_gem_object_create_region(mr, PAGE_SIZE, I915_BO_ALLOC_USER); | ||||
| 		if (obj == ERR_PTR(-ENODEV)) | ||||
| 			continue; | ||||
| 
 | ||||
| @ -1111,11 +1105,11 @@ static int __igt_mmap_gpu(struct drm_i915_private *i915, | ||||
| 			  enum i915_mmap_type type) | ||||
| { | ||||
| 	struct intel_engine_cs *engine; | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	unsigned long addr; | ||||
| 	u32 __user *ux; | ||||
| 	u32 bbe; | ||||
| 	int err; | ||||
| 	u64 offset; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Verify that the mmap access into the backing store aligns with | ||||
| @ -1132,11 +1126,11 @@ static int __igt_mmap_gpu(struct drm_i915_private *i915, | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, type, NULL); | ||||
| 	if (IS_ERR(mmo)) | ||||
| 		return PTR_ERR(mmo); | ||||
| 	err = __assign_mmap_offset(obj, type, &offset, NULL); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	addr = igt_mmap_node(i915, &mmo->vma_node, 0, PROT_WRITE, MAP_SHARED); | ||||
| 	addr = igt_mmap_offset(i915, offset, obj->base.size, PROT_WRITE, MAP_SHARED); | ||||
| 	if (IS_ERR_VALUE(addr)) | ||||
| 		return addr; | ||||
| 
 | ||||
| @ -1226,7 +1220,7 @@ static int igt_mmap_gpu(void *arg) | ||||
| 		struct drm_i915_gem_object *obj; | ||||
| 		int err; | ||||
| 
 | ||||
| 		obj = i915_gem_object_create_region(mr, PAGE_SIZE, 0); | ||||
| 		obj = i915_gem_object_create_region(mr, PAGE_SIZE, I915_BO_ALLOC_USER); | ||||
| 		if (obj == ERR_PTR(-ENODEV)) | ||||
| 			continue; | ||||
| 
 | ||||
| @ -1303,18 +1297,18 @@ static int __igt_mmap_revoke(struct drm_i915_private *i915, | ||||
| 			     struct drm_i915_gem_object *obj, | ||||
| 			     enum i915_mmap_type type) | ||||
| { | ||||
| 	struct i915_mmap_offset *mmo; | ||||
| 	unsigned long addr; | ||||
| 	int err; | ||||
| 	u64 offset; | ||||
| 
 | ||||
| 	if (!can_mmap(obj, type)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	mmo = mmap_offset_attach(obj, type, NULL); | ||||
| 	if (IS_ERR(mmo)) | ||||
| 		return PTR_ERR(mmo); | ||||
| 	err = __assign_mmap_offset(obj, type, &offset, NULL); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	addr = igt_mmap_node(i915, &mmo->vma_node, 0, PROT_WRITE, MAP_SHARED); | ||||
| 	addr = igt_mmap_offset(i915, offset, obj->base.size, PROT_WRITE, MAP_SHARED); | ||||
| 	if (IS_ERR_VALUE(addr)) | ||||
| 		return addr; | ||||
| 
 | ||||
| @ -1350,10 +1344,20 @@ static int __igt_mmap_revoke(struct drm_i915_private *i915, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = check_absent(addr, obj->base.size); | ||||
| 	if (err) { | ||||
| 		pr_err("%s: was not absent\n", obj->mm.region->name); | ||||
| 		goto out_unmap; | ||||
| 	if (!obj->ops->mmap_ops) { | ||||
| 		err = check_absent(addr, obj->base.size); | ||||
| 		if (err) { | ||||
| 			pr_err("%s: was not absent\n", obj->mm.region->name); | ||||
| 			goto out_unmap; | ||||
| 		} | ||||
| 	} else { | ||||
| 		/* ttm allows access to evicted regions by design */ | ||||
| 
 | ||||
| 		err = check_present(addr, obj->base.size); | ||||
| 		if (err) { | ||||
| 			pr_err("%s: was not present\n", obj->mm.region->name); | ||||
| 			goto out_unmap; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| out_unmap: | ||||
| @ -1371,7 +1375,7 @@ static int igt_mmap_revoke(void *arg) | ||||
| 		struct drm_i915_gem_object *obj; | ||||
| 		int err; | ||||
| 
 | ||||
| 		obj = i915_gem_object_create_region(mr, PAGE_SIZE, 0); | ||||
| 		obj = i915_gem_object_create_region(mr, PAGE_SIZE, I915_BO_ALLOC_USER); | ||||
| 		if (obj == ERR_PTR(-ENODEV)) | ||||
| 			continue; | ||||
| 
 | ||||
|  | ||||
| @ -9,6 +9,7 @@ | ||||
| #include "intel_region_ttm.h" | ||||
| #include "gem/i915_gem_lmem.h" | ||||
| #include "gem/i915_gem_region.h" | ||||
| #include "gem/i915_gem_ttm.h" | ||||
| #include "intel_region_lmem.h" | ||||
| 
 | ||||
| static int init_fake_lmem_bar(struct intel_memory_region *mem) | ||||
| @ -107,7 +108,7 @@ out_no_io: | ||||
| static const struct intel_memory_region_ops intel_region_lmem_ops = { | ||||
| 	.init = region_lmem_init, | ||||
| 	.release = region_lmem_release, | ||||
| 	.init_object = __i915_gem_lmem_object_init, | ||||
| 	.init_object = __i915_gem_ttm_object_init, | ||||
| }; | ||||
| 
 | ||||
| struct intel_memory_region * | ||||
|  | ||||
| @ -561,7 +561,7 @@ static int i915_driver_hw_probe(struct drm_i915_private *dev_priv) | ||||
| 	if (ret) | ||||
| 		goto err_perf; | ||||
| 
 | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "inteldrmfb"); | ||||
| 	ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, dev_priv->drm.driver); | ||||
| 	if (ret) | ||||
| 		goto err_ggtt; | ||||
| 
 | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user