[PATCH v2 1/7] ati-vga: Add scissor clipping register support

Chad Jablonski posted 7 patches 3 months, 1 week ago
There is a newer version of this series
[PATCH v2 1/7] ati-vga: Add scissor clipping register support
Posted by Chad Jablonski 3 months, 1 week ago
Implement read and write operations on SC_TOP_LEFT, SC_BOTTOM_RIGHT,
and SRC_SC_BOTTOM_RIGHT registers. These registers are also updated
when the src and/or dst clipping fields on DP_GUI_MASTER_CNTL are set
to default clipping.

Scissor clipping is used when rendering text in X.org. The r128 driver
sends host data much wider than is necessary to draw a glyph and cuts it
down to size using clipping before rendering. The actual clipping
implementation follows in a future patch.

Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
---
 hw/display/ati.c      | 26 ++++++++++++++++++++++++++
 hw/display/ati_int.h  |  3 +++
 hw/display/ati_regs.h | 12 ++++++++++--
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/hw/display/ati.c b/hw/display/ati.c
index 0b4298d078..eb9b30672f 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -510,6 +510,15 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size)
     case DEFAULT_SC_BOTTOM_RIGHT:
         val = s->regs.default_sc_bottom_right;
         break;
+    case SC_TOP_LEFT:
+        val = s->regs.sc_top_left;
+        break;
+    case SC_BOTTOM_RIGHT:
+        val = s->regs.sc_bottom_right;
+        break;
+    case SRC_SC_BOTTOM_RIGHT:
+        val = s->regs.src_sc_bottom_right;
+        break;
     default:
         break;
     }
@@ -862,6 +871,14 @@ static void ati_mm_write(void *opaque, hwaddr addr,
         s->regs.dp_datatype = (data & 0x0f00) >> 8 | (data & 0x30f0) << 4 |
                               (data & 0x4000) << 16;
         s->regs.dp_mix = (data & GMC_ROP3_MASK) | (data & 0x7000000) >> 16;
+
+        if ((data & GMC_SRC_CLIPPING_MASK) == GMC_SRC_CLIP_DEFAULT) {
+            s->regs.src_sc_bottom_right = s->regs.default_sc_bottom_right;
+        }
+        if ((data & GMC_DST_CLIPPING_MASK) == GMC_DST_CLIP_DEFAULT) {
+            s->regs.sc_top_left = 0;
+            s->regs.sc_bottom_right = s->regs.default_sc_bottom_right;
+        }
         break;
     case DST_WIDTH_X:
         s->regs.dst_x = data & 0x3fff;
@@ -937,6 +954,15 @@ static void ati_mm_write(void *opaque, hwaddr addr,
     case DEFAULT_SC_BOTTOM_RIGHT:
         s->regs.default_sc_bottom_right = data & 0x3fff3fff;
         break;
+    case SC_TOP_LEFT:
+        s->regs.sc_top_left = data;
+        break;
+    case SC_BOTTOM_RIGHT:
+        s->regs.sc_bottom_right = data;
+        break;
+    case SRC_SC_BOTTOM_RIGHT:
+        s->regs.src_sc_bottom_right = data;
+        break;
     default:
         break;
     }
diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h
index 708cc1dd3a..aab3cbf81a 100644
--- a/hw/display/ati_int.h
+++ b/hw/display/ati_int.h
@@ -86,6 +86,9 @@ typedef struct ATIVGARegs {
     uint32_t default_pitch;
     uint32_t default_tile;
     uint32_t default_sc_bottom_right;
+    uint32_t sc_top_left;
+    uint32_t sc_bottom_right;
+    uint32_t src_sc_bottom_right;
 } ATIVGARegs;
 
 struct ATIVGAState {
diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
index d7127748ff..2b56b9fb66 100644
--- a/hw/display/ati_regs.h
+++ b/hw/display/ati_regs.h
@@ -392,8 +392,6 @@
 /* DP_GUI_MASTER_CNTL bit constants */
 #define GMC_SRC_PITCH_OFFSET_CNTL               0x00000001
 #define GMC_DST_PITCH_OFFSET_CNTL               0x00000002
-#define GMC_SRC_CLIP_DEFAULT                    0x00000000
-#define GMC_DST_CLIP_DEFAULT                    0x00000000
 #define GMC_BRUSH_SOLIDCOLOR                    0x000000d0
 #define GMC_SRC_DSTCOLOR                        0x00003000
 #define GMC_BYTE_ORDER_MSB_TO_LSB               0x00000000
@@ -404,6 +402,16 @@
 #define GMC_WRITE_MASK_SET                      0x40000000
 #define GMC_DP_CONVERSION_TEMP_6500             0x00000000
 
+/* DP_GUI_MASTER_CNTL DP_SRC_CLIPPING named constants */
+#define GMC_SRC_CLIPPING_MASK                   0x00000004
+#define GMC_SRC_CLIP_DEFAULT                    0x00000000
+#define GMC_SRC_CLIP_LEAVE_ALONE                0x00000004
+
+/* DP_GUI_MASTER_CNTL DP_DST_CLIPPING named constants */
+#define GMC_DST_CLIPPING_MASK                   0x00000008
+#define GMC_DST_CLIP_DEFAULT                    0x00000000
+#define GMC_DST_CLIP_LEAVE_ALONE                0x00000008
+
 /* DP_GUI_MASTER_CNTL ROP3 named constants */
 #define GMC_ROP3_MASK                           0x00ff0000
 #define ROP3_BLACKNESS                          0x00000000
-- 
2.51.0
Re: [PATCH v2 1/7] ati-vga: Add scissor clipping register support
Posted by BALATON Zoltan 3 months, 1 week ago
On Sun, 2 Nov 2025, Chad Jablonski wrote:
> Implement read and write operations on SC_TOP_LEFT, SC_BOTTOM_RIGHT,
> and SRC_SC_BOTTOM_RIGHT registers. These registers are also updated
> when the src and/or dst clipping fields on DP_GUI_MASTER_CNTL are set
> to default clipping.
>
> Scissor clipping is used when rendering text in X.org. The r128 driver
> sends host data much wider than is necessary to draw a glyph and cuts it
> down to size using clipping before rendering. The actual clipping
> implementation follows in a future patch.
>
> Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
> ---
> hw/display/ati.c      | 26 ++++++++++++++++++++++++++
> hw/display/ati_int.h  |  3 +++
> hw/display/ati_regs.h | 12 ++++++++++--
> 3 files changed, 39 insertions(+), 2 deletions(-)
>
> diff --git a/hw/display/ati.c b/hw/display/ati.c
> index 0b4298d078..eb9b30672f 100644
> --- a/hw/display/ati.c
> +++ b/hw/display/ati.c
> @@ -510,6 +510,15 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size)
>     case DEFAULT_SC_BOTTOM_RIGHT:
>         val = s->regs.default_sc_bottom_right;
>         break;
> +    case SC_TOP_LEFT:
> +        val = s->regs.sc_top_left;
> +        break;
> +    case SC_BOTTOM_RIGHT:
> +        val = s->regs.sc_bottom_right;
> +        break;
> +    case SRC_SC_BOTTOM_RIGHT:
> +        val = s->regs.src_sc_bottom_right;
> +        break;
>     default:
>         break;
>     }
> @@ -862,6 +871,14 @@ static void ati_mm_write(void *opaque, hwaddr addr,
>         s->regs.dp_datatype = (data & 0x0f00) >> 8 | (data & 0x30f0) << 4 |
>                               (data & 0x4000) << 16;
>         s->regs.dp_mix = (data & GMC_ROP3_MASK) | (data & 0x7000000) >> 16;
> +
> +        if ((data & GMC_SRC_CLIPPING_MASK) == GMC_SRC_CLIP_DEFAULT) {
> +            s->regs.src_sc_bottom_right = s->regs.default_sc_bottom_right;
> +        }
> +        if ((data & GMC_DST_CLIPPING_MASK) == GMC_DST_CLIP_DEFAULT) {
> +            s->regs.sc_top_left = 0;
> +            s->regs.sc_bottom_right = s->regs.default_sc_bottom_right;
> +        }

Or is this what you meant by style? Now I get that. I think the bits 
should not reset the regs just cause the operation to use the default 
values instead but if you can verify what actual hardware does that would 
be best.

Regards,
BALATON Zoltan

>         break;
>     case DST_WIDTH_X:
>         s->regs.dst_x = data & 0x3fff;
> @@ -937,6 +954,15 @@ static void ati_mm_write(void *opaque, hwaddr addr,
>     case DEFAULT_SC_BOTTOM_RIGHT:
>         s->regs.default_sc_bottom_right = data & 0x3fff3fff;
>         break;
> +    case SC_TOP_LEFT:
> +        s->regs.sc_top_left = data;
> +        break;
> +    case SC_BOTTOM_RIGHT:
> +        s->regs.sc_bottom_right = data;
> +        break;
> +    case SRC_SC_BOTTOM_RIGHT:
> +        s->regs.src_sc_bottom_right = data;
> +        break;
>     default:
>         break;
>     }
> diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h
> index 708cc1dd3a..aab3cbf81a 100644
> --- a/hw/display/ati_int.h
> +++ b/hw/display/ati_int.h
> @@ -86,6 +86,9 @@ typedef struct ATIVGARegs {
>     uint32_t default_pitch;
>     uint32_t default_tile;
>     uint32_t default_sc_bottom_right;
> +    uint32_t sc_top_left;
> +    uint32_t sc_bottom_right;
> +    uint32_t src_sc_bottom_right;
> } ATIVGARegs;
>
> struct ATIVGAState {
> diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
> index d7127748ff..2b56b9fb66 100644
> --- a/hw/display/ati_regs.h
> +++ b/hw/display/ati_regs.h
> @@ -392,8 +392,6 @@
> /* DP_GUI_MASTER_CNTL bit constants */
> #define GMC_SRC_PITCH_OFFSET_CNTL               0x00000001
> #define GMC_DST_PITCH_OFFSET_CNTL               0x00000002
> -#define GMC_SRC_CLIP_DEFAULT                    0x00000000
> -#define GMC_DST_CLIP_DEFAULT                    0x00000000
> #define GMC_BRUSH_SOLIDCOLOR                    0x000000d0
> #define GMC_SRC_DSTCOLOR                        0x00003000
> #define GMC_BYTE_ORDER_MSB_TO_LSB               0x00000000
> @@ -404,6 +402,16 @@
> #define GMC_WRITE_MASK_SET                      0x40000000
> #define GMC_DP_CONVERSION_TEMP_6500             0x00000000
>
> +/* DP_GUI_MASTER_CNTL DP_SRC_CLIPPING named constants */
> +#define GMC_SRC_CLIPPING_MASK                   0x00000004
> +#define GMC_SRC_CLIP_DEFAULT                    0x00000000
> +#define GMC_SRC_CLIP_LEAVE_ALONE                0x00000004
> +
> +/* DP_GUI_MASTER_CNTL DP_DST_CLIPPING named constants */
> +#define GMC_DST_CLIPPING_MASK                   0x00000008
> +#define GMC_DST_CLIP_DEFAULT                    0x00000000
> +#define GMC_DST_CLIP_LEAVE_ALONE                0x00000008
> +
> /* DP_GUI_MASTER_CNTL ROP3 named constants */
> #define GMC_ROP3_MASK                           0x00ff0000
> #define ROP3_BLACKNESS                          0x00000000
>
Re: [PATCH v2 1/7] ati-vga: Add scissor clipping register support
Posted by Chad Jablonski 3 months, 1 week ago
>> +        if ((data & GMC_SRC_CLIPPING_MASK) == GMC_SRC_CLIP_DEFAULT) {
>> +            s->regs.src_sc_bottom_right = s->regs.default_sc_bottom_right;
>> +        }
>> +        if ((data & GMC_DST_CLIPPING_MASK) == GMC_DST_CLIP_DEFAULT) {
>> +            s->regs.sc_top_left = 0;
>> +            s->regs.sc_bottom_right = s->regs.default_sc_bottom_right;
>> +        }
>
> Or is this what you meant by style? Now I get that. I think the bits 
> should not reset the regs just cause the operation to use the default 
> values instead but if you can verify what actual hardware does that would 
> be best.

Hi BALATON,

I've tested this on the real 'Rage 128 Pro Ultra TF' and it shows that this is
the correct behavior (copying to the registers). I was definitely a bit
surprised!

I've run this test both with X running (idle over ssh) and prior to
starting X with the same results. I would like to write a bare-metal test
to make sure that the environment is a bit more controlled. But I struggled
with modesetting and it was looking like it was going to become a bit of a
rabbit hole. So I settled on testing under Linux for now and left that for
another day.

Here is the output:

Test SRC clipping
====================================

** Initializing DEFAULT_SC_BOTTOM_RIGHT to 0x0 **
** Initializing SRC_SC_BOTTOM to 0x0 **
** Initializing SRC_SC_RIGHT to 0x0 **

Initial State
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00f
DEFAULT_SC_BOTTOM_RIGHT: 0x00000000
SRC_SC_BOTTOM:           0x00000000
SRC_SC_RIGHT:            0x00000000

** Setting DEFAULT_SC_BOTTOM_RIGHT to 0x0aaa0bbb **
** Setting SRC_SC_BOTTOM to 0x111 **
** Setting SRC_SC_RIGHT to 0x222 **

State After Setting
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00f
DEFAULT_SC_BOTTOM_RIGHT: 0x0aaa0bbb
SRC_SC_BOTTOM:           0x00000111
SRC_SC_RIGHT:            0x00000222

** Setting GMC_SRC_CLIPPING to default **

State After Setting Default
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00b
DEFAULT_SC_BOTTOM_RIGHT: 0x0aaa0bbb
SRC_SC_BOTTOM:           0x00000aaa  <======= Set to default
SRC_SC_RIGHT:            0x00000bbb  <======= Set to default

** Setting GMC_SRC_CLIPPING to leave alone **

State After Setting Leave Alone
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00f
DEFAULT_SC_BOTTOM_RIGHT: 0x0aaa0bbb
SRC_SC_BOTTOM:           0x00000aaa  <======= STILL default
SRC_SC_RIGHT:            0x00000bbb  <======= STILL default

Test DST clipping
====================================

** Initializing DEFAULT_SC_BOTTOM_RIGHT to 0x0 **
** Initializing SC_BOTTOM to 0x0 **
** Initializing SC_RIGHT to 0x0 **
** Initializing SC_TOP to 0x0 **
** Initializing SC_LEFT to 0x0 **

Initial State
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00f
DEFAULT_SC_BOTTOM_RIGHT: 0x00000000
SC_BOTTOM:               0x00000000
SC_RIGHT:                0x00000000
SC_TOP:                  0x00000000
SC_LEFT:                 0x00000000

** Setting DEFAULT_SC_BOTTOM_RIGHT to 0x0aaa0bbb **
** Setting SC_BOTTOM to 0x111 **
** Setting SC_RIGHT to 0x222 **
** SETTING SC_TOP to 0x333 **
** SETTING SC_LEFT to 0x444 **

State After Setting
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00f
DEFAULT_SC_BOTTOM_RIGHT: 0x0aaa0bbb
SC_BOTTOM:               0x00000111
SC_RIGHT:                0x00000222
SC_TOP:                  0x00000333
SC_LEFT:                 0x00000444

** Setting GMC_DST_CLIPPING to default **

State After Setting Default
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de007
DEFAULT_SC_BOTTOM_RIGHT: 0x0aaa0bbb
SC_BOTTOM:               0x00000aaa  <======= Set to default
SC_RIGHT:                0x00000bbb  <======= Set to default
SC_TOP:                  0x00000000  <======= Set to default
SC_LEFT:                 0x00000000  <======= Set to default

** Setting GMC_DST_CLIPPING to leave alone **

State After Setting Leave Alone
------------------------------------
DEFAULT_GUI_MASTER_CNTL: 0x4a1de00f
DEFAULT_SC_BOTTOM_RIGHT: 0x0aaa0bbb
SC_BOTTOM:               0x00000aaa  <======= STILL default
SC_RIGHT:                0x00000bbb  <======= STILL default
SC_TOP:                  0x00000000  <======= STILL default
SC_LEFT:                 0x00000000  <======= STILL default


And the source:

===============================================================================

/*
 * ATI Rage 128 Pro Clipping Mode Hardware Test
 *
 * Tests whether clipping mode bits exhibit latching or dynamic behavior
 *
 * Build: gcc -std=c99 -o test test.c -lpci
 * Requirements: libpci-dev, root privileges, ATI Rage 128 Pro hardware
 * Note: Run with X.org idle (SSH session recommended)
 */
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <pci/pci.h>

#define ATI_VENDOR_ID            0x1002
#define MAX_ATI_DEVICES          10

#define DP_GUI_MASTER_CNTL       0x146c

#define SRC_SC_BOTTOM_RIGHT      0x16f4
#define SRC_SC_BOTTOM            0x165C
#define SRC_SC_RIGHT             0x1654
#define DEFAULT_SC_BOTTOM_RIGHT  0x16e8

#define SC_TOP_LEFT              0x1640
#define SC_LEFT                  0x1640
#define SC_TOP                   0x1648

#define SC_BOTTOM_RIGHT          0x1644
#define SC_RIGHT                 0x1644
#define SC_BOTTOM                0x164C

#define DST_OFFSET               0x1404
#define DST_PITCH                0x1408
#define DP_BRUSH_FRGD_CLR        0x147c
#define DP_WRITE_MASK            0x16cc
#define DST_HEIGHT               0x1410
#define DST_X_Y                  0x1594
#define DST_WIDTH_X              0x1588

#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
  __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)

void run_tests(void *bar2);
struct pci_dev *find_device(struct pci_access *pacc,
                            char *name_out, int name_len);
void print_devices(struct pci_access *pacc);
void *map_bar(struct pci_dev *dev, int bar_idx);
static inline uint32_t reg_read(void *base, uint32_t offset);
static inline uint32_t reg_write(void *base, uint32_t offset, uint32_t value);

int main(int argc, char **argv) {
  struct pci_access *pacc = pci_alloc();
  char name[256];
  struct pci_dev *dev = find_device(pacc, name, sizeof(name));
  void *bar2 = map_bar(dev, 2);

  run_tests(bar2);

  return 0;
}

void test_dst_clipping(void *bar2) {
  uint32_t dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  uint32_t default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  uint32_t sc_bottom = reg_read(bar2, SC_BOTTOM);
  uint32_t sc_right = reg_read(bar2, SC_RIGHT);
  uint32_t sc_top = reg_read(bar2, SC_TOP);
  uint32_t sc_left = reg_read(bar2, SC_LEFT);

  printf("Test DST clipping\n");
  printf("====================================\n\n");

  printf("** Initializing DEFAULT_SC_BOTTOM_RIGHT to 0x0 **\n");
  printf("** Initializing SC_BOTTOM to 0x0 **\n");
  printf("** Initializing SC_RIGHT to 0x0 **\n");
  printf("** Initializing SC_TOP to 0x0 **\n");
  printf("** Initializing SC_LEFT to 0x0 **\n");

  reg_write(bar2, DEFAULT_SC_BOTTOM_RIGHT, 0x0);
  reg_write(bar2, SC_BOTTOM, 0x0);
  reg_write(bar2, SC_RIGHT, 0x0);
  reg_write(bar2, SC_TOP, 0x0);
  reg_write(bar2, SC_LEFT, 0x0);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  sc_bottom = reg_read(bar2, SC_BOTTOM);
  sc_right = reg_read(bar2, SC_RIGHT);
  sc_top = reg_read(bar2, SC_TOP);
  sc_left = reg_read(bar2, SC_LEFT);

  printf("\n");
  printf("Initial State\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SC_BOTTOM:               0x%08x\n", sc_bottom);
  printf("SC_RIGHT:                0x%08x\n", sc_right);
  printf("SC_TOP:                  0x%08x\n", sc_top);
  printf("SC_LEFT:                 0x%08x\n", sc_left);
  printf("\n");

  printf("** Setting DEFAULT_SC_BOTTOM_RIGHT to 0x0aaa0bbb **\n");
  printf("** Setting SC_BOTTOM to 0x111 **\n");
  printf("** Setting SC_RIGHT to 0x222 **\n");
  printf("** SETTING SC_TOP to 0x333 **\n");
  printf("** SETTING SC_LEFT to 0x444 **\n");
  reg_write(bar2, DEFAULT_SC_BOTTOM_RIGHT, 0x0aaa0bbb);
  reg_write(bar2, SC_BOTTOM, 0x00000111);
  reg_write(bar2, SC_RIGHT, 0x00000222);
  reg_write(bar2, SC_TOP, 0x00000333);
  reg_write(bar2, SC_LEFT, 0x00000444);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  sc_bottom = reg_read(bar2, SC_BOTTOM);
  sc_right = reg_read(bar2, SC_RIGHT);
  sc_top = reg_read(bar2, SC_TOP);
  sc_left = reg_read(bar2, SC_LEFT);

  printf("\n");
  printf("State After Setting\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SC_BOTTOM:               0x%08x\n", sc_bottom);
  printf("SC_RIGHT:                0x%08x\n", sc_right);
  printf("SC_TOP:                  0x%08x\n", sc_top);
  printf("SC_LEFT:                 0x%08x\n", sc_left);
  printf("\n");

  printf("** Setting GMC_DST_CLIPPING to default **\n");
  reg_write(bar2, DP_GUI_MASTER_CNTL, dp_gui_master_cntl & ~0x8);
  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  sc_bottom = reg_read(bar2, SC_BOTTOM);
  sc_right = reg_read(bar2, SC_RIGHT);
  sc_top = reg_read(bar2, SC_TOP);
  sc_left = reg_read(bar2, SC_LEFT);

  printf("\n");
  printf("State After Setting Default\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SC_BOTTOM:               0x%08x\n", sc_bottom);
  printf("SC_RIGHT:                0x%08x\n", sc_right);
  printf("SC_TOP:                  0x%08x\n", sc_top);
  printf("SC_LEFT:                 0x%08x\n", sc_left);
  printf("\n");

  printf("** Setting GMC_DST_CLIPPING to leave alone **\n");
  reg_write(bar2, DP_GUI_MASTER_CNTL, dp_gui_master_cntl | 0x8);
  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  sc_bottom = reg_read(bar2, SC_BOTTOM);
  sc_right = reg_read(bar2, SC_RIGHT);
  sc_top = reg_read(bar2, SC_TOP);
  sc_left = reg_read(bar2, SC_LEFT);

  printf("\n");
  printf("State After Setting Leave Alone\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SC_BOTTOM:               0x%08x\n", sc_bottom);
  printf("SC_RIGHT:                0x%08x\n", sc_right);
  printf("SC_TOP:                  0x%08x\n", sc_top);
  printf("SC_LEFT:                 0x%08x\n", sc_left);
  printf("\n");
}

void test_src_clipping(void *bar2) {
  uint32_t dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  uint32_t default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  uint32_t src_sc_bottom = reg_read(bar2, SRC_SC_BOTTOM);
  uint32_t src_sc_right = reg_read(bar2, SRC_SC_RIGHT);

  printf("Test SRC clipping\n");
  printf("====================================\n\n");

  printf("** Initializing DEFAULT_SC_BOTTOM_RIGHT to 0x0 **\n");
  printf("** Initializing SRC_SC_BOTTOM to 0x0 **\n");
  printf("** Initializing SRC_SC_RIGHT to 0x0 **\n");

  reg_write(bar2, DEFAULT_SC_BOTTOM_RIGHT, 0x0);
  reg_write(bar2, SRC_SC_BOTTOM, 0x0);
  reg_write(bar2, SRC_SC_RIGHT, 0x0);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  src_sc_bottom = reg_read(bar2, SRC_SC_BOTTOM);
  src_sc_right = reg_read(bar2, SRC_SC_RIGHT);

  printf("\n");
  printf("Initial State\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SRC_SC_BOTTOM:           0x%08x\n", src_sc_bottom);
  printf("SRC_SC_RIGHT:            0x%08x\n", src_sc_right);
  printf("\n");

  printf("** Setting DEFAULT_SC_BOTTOM_RIGHT to 0x0aaa0bbb **\n");
  printf("** Setting SRC_SC_BOTTOM to 0x111 **\n");
  printf("** Setting SRC_SC_RIGHT to 0x222 **\n");
  reg_write(bar2, DEFAULT_SC_BOTTOM_RIGHT, 0x0aaa0bbb);
  reg_write(bar2, SRC_SC_BOTTOM, 0x00000111);
  reg_write(bar2, SRC_SC_RIGHT, 0x00000222);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  src_sc_bottom = reg_read(bar2, SRC_SC_BOTTOM);
  src_sc_right = reg_read(bar2, SRC_SC_RIGHT);

  printf("\n");
  printf("State After Setting\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SRC_SC_BOTTOM:           0x%08x\n", src_sc_bottom);
  printf("SRC_SC_RIGHT:            0x%08x\n", src_sc_right);
  printf("\n");

  printf("** Setting GMC_SRC_CLIPPING to default **\n");
  reg_write(bar2, DP_GUI_MASTER_CNTL, dp_gui_master_cntl & ~0x4);
  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  src_sc_bottom = reg_read(bar2, SRC_SC_BOTTOM);
  src_sc_right = reg_read(bar2, SRC_SC_RIGHT);

  printf("\n");
  printf("State After Setting Default\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SRC_SC_BOTTOM:           0x%08x\n", src_sc_bottom);
  printf("SRC_SC_RIGHT:            0x%08x\n", src_sc_right);
  printf("\n");

  printf("** Setting GMC_SRC_CLIPPING to leave alone **\n");
  reg_write(bar2, DP_GUI_MASTER_CNTL, dp_gui_master_cntl | 0x4);
  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);

  dp_gui_master_cntl = reg_read(bar2, DP_GUI_MASTER_CNTL);
  default_sc_bottom_right = reg_read(bar2, DEFAULT_SC_BOTTOM_RIGHT);
  src_sc_bottom = reg_read(bar2, SRC_SC_BOTTOM);
  src_sc_right = reg_read(bar2, SRC_SC_RIGHT);

  printf("\n");
  printf("State After Setting Leave Alone\n");
  printf("------------------------------------\n");
  printf("DEFAULT_GUI_MASTER_CNTL: 0x%08x\n", dp_gui_master_cntl);
  printf("DEFAULT_SC_BOTTOM_RIGHT: 0x%08x\n", default_sc_bottom_right);
  printf("SRC_SC_BOTTOM:           0x%08x\n", src_sc_bottom);
  printf("SRC_SC_RIGHT:            0x%08x\n", src_sc_right);
  printf("\n");
}

void run_tests(void *bar2) {
  test_src_clipping(bar2);
  test_dst_clipping(bar2);
}

struct pci_dev *find_device(struct pci_access *pacc,
                            char *name_out, int name_len) {
  struct pci_dev *dev, *it;
  int device_count = 0;

  pci_init(pacc);
  pci_scan_bus(pacc);

  for (it = pacc->devices; it; it = it->next) {
    if (it->vendor_id == ATI_VENDOR_ID) {
      if (device_count == 0) {
        dev = it;
      }
      device_count += 1;
    }
  }

  if (device_count == 0) {
    printf("No ATI devices found\n");
    exit(1);
  }

  if (device_count > 1) {
    printf("Found multiple ATI devices:\n");
    print_devices(pacc);
  }

  pci_lookup_name(pacc, name_out, name_len,
                  PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE,
                  dev->vendor_id, dev->device_id);

  printf("# %s\n\n", name_out);

  return dev;
}

void print_devices(struct pci_access *pacc) {
  struct pci_dev *dev;
  char name[256];

  for (dev = pacc->devices; dev; dev = dev->next) {
    if (dev->vendor_id != ATI_VENDOR_ID) continue;
    pci_lookup_name(pacc, name, sizeof(name),
                    PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE,
                    dev->vendor_id, dev->device_id);
    printf("\t- %s\n", name);
  }
}

void *map_bar(struct pci_dev *dev, int bar_idx) {
  char pci_loc[32];
  sprintf(pci_loc, "%04x:%02x:%02x.%d", dev->domain, dev->bus,
          dev->dev, dev->func);

  char base_path[256];
  sprintf(base_path, "/sys/bus/pci/devices/%s", pci_loc);

  char bar_path[256];
  sprintf(bar_path, "%s/resource%d", base_path, bar_idx);

  int bar_fd = open(bar_path, O_RDWR | O_SYNC);
  if (bar_fd == -1) FATAL;

  void *bar = mmap(NULL, dev->size[bar_idx], PROT_READ | PROT_WRITE,
                   MAP_SHARED, bar_fd, 0);
  if (bar == (void *) -1) FATAL;

  return bar;
}

static inline uint32_t reg_read(void *base, uint32_t offset) {
  volatile uint32_t *reg = (volatile uint32_t *)(base + offset);
  return *reg;
}

static inline uint32_t reg_write(void *base, uint32_t offset, uint32_t value) {
  volatile uint32_t *reg = (volatile uint32_t *)(base + offset);
  *reg = value;
}

===============================================================================

I haven't tested them yet, but I'll also run this test for
GMC_SRC_PITCH_OFFSET_CNTL and GMC_DST_PITCH_OFFSET_CNTL to confirm that
they have the same behavior.