diff --git a/configs/targets/aarch64_ilp32-linux-user.mak b/configs/targets/aarch64_ilp32-linux-user.mak
new file mode 100644
index 0000000000..e70fd98eef
--- /dev/null
+++ b/configs/targets/aarch64_ilp32-linux-user.mak
@@ -0,0 +1,10 @@
+TARGET_ARCH=aarch64
+TARGET_BASE_ARCH=arm
+TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml gdb-xml/aarch64-pauth.xml gdb-xml/aarch64-mte.xml
+TARGET_HAS_BFLT=y
+CONFIG_SEMIHOSTING=y
+CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
+TARGET_SYSTBL_ABI=common,32,time32,stat64,renameat,rlimit,memfd_secret
+TARGET_SYSTBL=syscall_64.tbl
+TARGET_LONG_BITS=64
+TARGET_ABI32=y
diff --git a/linux-user/aarch64/syscall_64.tbl b/linux-user/aarch64/syscall_64.tbl
index 845e24eb37..33507d823b 100644
--- a/linux-user/aarch64/syscall_64.tbl
+++ b/linux-user/aarch64/syscall_64.tbl
@@ -265,7 +265,7 @@
 221	common	execve				sys_execve			compat_sys_execve
 222	32	mmap2				sys_mmap2
 222	64	mmap				sys_mmap
-223	32	fadvise64_64			sys_fadvise64_64		compat_sys_fadvise64_64
+223	32	arm_fadvise64_64		sys_arm_fadvise64_64
 223	64	fadvise64			sys_fadvise64_64
 224	common	swapon				sys_swapon
 225	common	swapoff				sys_swapoff
diff --git a/linux-user/aarch64/target_mman.h b/linux-user/aarch64/target_mman.h
index 69ec5d5739..7356235a40 100644
--- a/linux-user/aarch64/target_mman.h
+++ b/linux-user/aarch64/target_mman.h
@@ -4,6 +4,7 @@
 #define TARGET_PROT_BTI         0x10
 #define TARGET_PROT_MTE         0x20
 
+#ifndef TARGET_ABI32
 /*
  * arch/arm64/include/asm/processor.h:
  *
@@ -16,6 +17,11 @@
 
 /* arch/arm64/include/asm/elf.h */
 #define ELF_ET_DYN_BASE         TARGET_PAGE_ALIGN((1ull << 48) / 3 * 2)
+#else
+/* aarch64_ilp32 */
+#define TASK_UNMAPPED_BASE      (1ull << (30))
+#define ELF_ET_DYN_BASE         TARGET_PAGE_ALIGN((1ull << 30) / 3 * 2)
+#endif
 
 #include "../generic/target_mman.h"
 
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index a2c152e5ad..70d2913915 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -682,7 +682,11 @@ static const VdsoImageInfo *vdso_image_info(uint32_t elf_flags)
 /* 64 bit ARM definitions */
 
 #define ELF_ARCH        EM_AARCH64
+#ifndef TARGET_ABI32
 #define ELF_CLASS       ELFCLASS64
+#else
+#define ELF_CLASS       ELFCLASS32
+#endif
 #if TARGET_BIG_ENDIAN
 # define ELF_PLATFORM    "aarch64_be"
 #else
@@ -977,11 +981,13 @@ const char *elf_hwcap2_str(uint32_t bit)
 
 #undef GET_FEATURE_ID
 
+#ifndef TARGET_ABI32
 #if TARGET_BIG_ENDIAN
 # define VDSO_HEADER  "vdso-be.c.inc"
 #else
 # define VDSO_HEADER  "vdso-le.c.inc"
 #endif
+#endif
 
 #endif /* not TARGET_AARCH64 */
 
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 5f00750151..1acf50b2b7 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -86,7 +86,7 @@ struct vm86_saved_state {
 };
 #endif
 
-#if defined(TARGET_ARM) && defined(TARGET_ABI32)
+#if defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64)
 /* FPU emulator */
 #include "nwfpe/fpa11.h"
 #endif
@@ -98,7 +98,7 @@ struct emulated_sigtable {
 
 struct TaskState {
     pid_t ts_tid;     /* tid (or pid) of this task */
-#ifdef TARGET_ARM
+#if defined(TARGET_ARM) && !defined(TARGET_AARCH64)
 # ifdef TARGET_ABI32
     /* FPA state */
     FPA11 fpa;
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 02ea4221c9..1012686227 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -6936,7 +6936,7 @@ static inline abi_long copy_to_user_flock(abi_ulong target_flock_addr,
 typedef abi_long from_flock64_fn(struct flock *fl, abi_ulong target_addr);
 typedef abi_long to_flock64_fn(abi_ulong target_addr, const struct flock *fl);
 
-#if defined(TARGET_ARM) && TARGET_ABI_BITS == 32
+#if defined(TARGET_ARM) && TARGET_ABI_BITS == 32 && !defined(TARGET_AARCH64)
 struct target_oabi_flock64 {
     abi_short l_type;
     abi_short l_whence;
@@ -7642,7 +7642,7 @@ static inline abi_long host_to_target_stat64(CPUArchState *cpu_env,
                                              abi_ulong target_addr,
                                              struct stat *host_st)
 {
-#if defined(TARGET_ARM) && defined(TARGET_ABI32)
+#if defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64)
     if (cpu_env->eabi) {
         struct target_eabi_stat64 *target_st;
 
@@ -12533,7 +12533,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,
         from_flock64_fn *copyfrom = copy_from_user_flock64;
         to_flock64_fn *copyto = copy_to_user_flock64;
 
-#ifdef TARGET_ARM
+#if defined(TARGET_ARM) && !defined(TARGET_AARCH64)
         if (!cpu_env->eabi) {
             copyfrom = copy_from_user_oabi_flock64;
             copyto = copy_to_user_oabi_flock64;
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
index 86d773add7..292939575c 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -60,7 +60,7 @@
 #define TARGET_IOC_TYPEBITS     8
 
 #if (defined(TARGET_I386) && defined(TARGET_ABI32))                     \
-    || (defined(TARGET_ARM) && defined(TARGET_ABI32))                   \
+    || (defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64))                   \
     || (defined(TARGET_SPARC) && defined(TARGET_ABI32))                 \
     || defined(TARGET_M68K) || defined(TARGET_SH4)
 /* 16 bit uid wrappers emulation */
@@ -1234,7 +1234,7 @@ struct target_winsize {
 #include "target_mman.h"
 
 #if (defined(TARGET_I386) && defined(TARGET_ABI32))     \
-    || (defined(TARGET_ARM) && defined(TARGET_ABI32))
+    || (defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64))
 #define TARGET_STAT_HAVE_NSEC
 struct target_stat {
     abi_ushort st_dev;
@@ -1905,7 +1905,7 @@ struct target_stat {
     abi_long       st_blocks;
     abi_ulong  __unused[3];
 };
-#elif defined(TARGET_AARCH64)
+#elif defined(TARGET_AARCH64) && !defined(TARGET_ABI32)
 #define TARGET_STAT_HAVE_NSEC
 struct target_stat {
     abi_ulong  st_dev;
@@ -1928,6 +1928,65 @@ struct target_stat {
     abi_ulong  target_st_ctime_nsec;
     abi_uint __unused[2];
 };
+#elif defined(TARGET_AARCH64) && defined(TARGET_ABI32)
+#define TARGET_STAT_HAVE_NSEC
+struct target_stat {
+    abi_ulong  st_dev;
+    abi_ulong  __ilp32_pad1;
+    abi_ulong  st_ino;
+    abi_ulong  __ilp32_pad2;
+    abi_uint   st_mode;
+    abi_uint   st_nlink;
+    abi_uint   st_uid;
+    abi_uint   st_gid;
+    abi_ulong  st_rdev;
+    abi_ulong  __ilp32_pad3;
+    abi_ulong  _pad1;
+    abi_ulong  __ilp32_pad4;
+    abi_long  st_size;
+    abi_ulong  __ilp32_pad5;
+    abi_int    st_blksize;
+    abi_int    __pad2;
+    abi_long   st_blocks;
+    abi_ulong __ilp32_pad6;
+    abi_long  target_st_atime;
+    abi_ulong  target_st_atime_nsec;
+    abi_long  target_st_mtime;
+    abi_ulong  target_st_mtime_nsec;
+    abi_long  target_st_ctime;
+    abi_ulong  target_st_ctime_nsec;
+    abi_uint __unused[2];
+};
+
+#define TARGET_HAS_STRUCT_STAT64
+struct target_stat64 {
+    abi_ulong  st_dev;
+    abi_ulong  __ilp32_pad1;
+#define TARGET_STAT64_HAS_BROKEN_ST_INO 1
+    abi_ulong  __st_ino;
+    abi_ulong  __ilp32_pad2;
+    abi_uint   st_mode;
+    abi_uint   st_nlink;
+    abi_uint   st_uid;
+    abi_uint   st_gid;
+    abi_ulong  st_rdev;
+    abi_ulong  __ilp32_pad3;
+    abi_ulong  __pad1;
+    abi_ulong  __ilp32_pad4;
+    abi_llong  st_size;
+    abi_int    st_blksize;
+    abi_int    _pad2;
+    abi_long   st_blocks;
+    abi_ulong  __ilp32_pad5;
+    abi_long   target_st_atime;
+    abi_ulong  target_st_atime_nsec;
+    abi_long   target_st_mtime;
+    abi_ulong  target_st_mtime_nsec;
+    abi_long   target_st_ctime;
+    abi_ulong  target_st_ctime_nsec;
+    abi_ullong st_ino;
+} QEMU_PACKED;
+
 #elif defined(TARGET_XTENSA)
 #define TARGET_STAT_HAVE_NSEC
 struct target_stat {
diff --git a/linux-user/user-internals.h b/linux-user/user-internals.h
index b9b05c1d11..b13ca36577 100644
--- a/linux-user/user-internals.h
+++ b/linux-user/user-internals.h
@@ -130,8 +130,10 @@ static inline uint64_t target_offset64(uint64_t word0, uint64_t word1)
 
 void print_termios(void *arg);
 
+#if (TARGET_ABI_BITS == 32) && defined(TARGET_AARCH64)
+static inline int regpairs_aligned(CPUArchState *cpu_env, int num) { return 1; }
 /* ARM EABI and MIPS expect 64bit types aligned even on pairs or registers */
-#ifdef TARGET_ARM
+#elif TARGET_ARM
 static inline int regpairs_aligned(CPUArchState *cpu_env, int num)
 {
     return cpu_env->eabi;
diff --git a/scripts/qemu-binfmt-conf.sh b/scripts/qemu-binfmt-conf.sh
index 6ef9f118d9..695d15e6ea 100755
--- a/scripts/qemu-binfmt-conf.sh
+++ b/scripts/qemu-binfmt-conf.sh
@@ -104,6 +104,10 @@ aarch64_be_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
 aarch64_be_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'
 aarch64_be_family=armeb
 
+aarch64_ilp32_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'
+aarch64_ilp32_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'
+aarch64_ilp32_family=arm
+
 hppa_magic='\x7f\x45\x4c\x46\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x0f'
 hppa_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'
 hppa_family=hppa
@@ -159,7 +163,7 @@ qemu_get_family() {
     ppc64el|ppc64le)
         echo "ppcle"
         ;;
-    arm|armel|armhf|arm64|armv[4-9]*l|aarch64)
+    arm|armel|armhf|arm64|armv[4-9]*l|aarch64|aarch64_ilp32)
         echo "arm"
         ;;
     armeb|armv[4-9]*b|aarch64_be)
diff --git a/target/arm/cpu-param.h b/target/arm/cpu-param.h
index 896b35bd6d..151d512e91 100644
--- a/target/arm/cpu-param.h
+++ b/target/arm/cpu-param.h
@@ -8,9 +8,13 @@
 #ifndef ARM_CPU_PARAM_H
 #define ARM_CPU_PARAM_H
 
-#ifdef TARGET_AARCH64
+#if defined(TARGET_AARCH64) && !defined(TARGET_ABI32)
 # define TARGET_PHYS_ADDR_SPACE_BITS  52
 # define TARGET_VIRT_ADDR_SPACE_BITS  52
+#elif defined(TARGET_AARCH64) && defined(TARGET_ABI32)
+# define TARGET_LONG_BITS             64
+# define TARGET_PHYS_ADDR_SPACE_BITS  40
+# define TARGET_VIRT_ADDR_SPACE_BITS  32
 #else
 # define TARGET_PHYS_ADDR_SPACE_BITS  40
 # define TARGET_VIRT_ADDR_SPACE_BITS  32
