This guide configures a Raspberry Pi 5 to be controlled directly by an iPad Pro using a single USB-C connection. It sets the Pi up as a USB Ethernet Gadget with a built-in DHCP server, so the iPad automatically gets an IP address when plugged in.

🛠 Hardware Requirements

Critical Warning: The Raspberry Pi 5 requires 5V/3A to 5V/5A to run stably. A standard USB cable between the iPad and Pi usually limits power to ~4.5W (0.9A), which may cause the Pi to crash.

Use one of these two setups:

Option A: The Magic Keyboard (Best Method)

  1. Power: Plug your iPad charger into the Magic Keyboard hinge port.
  2. Data: Connect a USB-C cable from the iPad's main port to the Pi's USB-C power port.

Option B: Powered USB-C Hub

  1. Hub: Use a hub with Pass-Through Charging (PD).
  2. Power: Wall Charger → Hub Input.
  3. Data: Hub Cable → iPad.
  4. Pi: Hub Downstream Port → Raspberry Pi 5 USB-C Power Port.

💻 Software Configuration (Ansible)

We will use Ansible to configure the kernel modules (dwc2, g_ether), install a DHCP server (dnsmasq), and manage networking.

Supported OS: Raspberry Pi OS (Bookworm), Ubuntu Server 22.04/24.04.

1. Create the Playbook

On your Raspberry Pi (or the machine controlling it), create a file named setup_ipad.yml:

---
- name: Configure Pi for iPad OTG with DHCP and NetworkManager
  hosts: localhost
  connection: local
  become: yes
  vars:
    # The static IP for the Raspberry Pi on the USB connection
    otg_ip: "10.55.0.1/24"
    # The IP range assigned to the iPad
    dhcp_start: "10.55.0.2"
    dhcp_end: "10.55.0.6"

  tasks:
    # --- KERNEL CONFIGURATION ---
    - name: Detect Boot Configuration Path
      ansible.builtin.stat:
        path: "/boot/firmware/config.txt"
      register: firmware_config

    - name: Set Boot Path Variable
      set_fact:
        boot_path: "{{ '/boot/firmware' if firmware_config.stat.exists else '/boot' }}"

    - name: Enable 'dwc2' overlay in config.txt
      ansible.builtin.lineinfile:
        path: "{{ boot_path }}/config.txt"
        regexp: '^dtoverlay=dwc2'
        line: 'dtoverlay=dwc2'
        state: present
      notify: Reboot Pi

    - name: Check for modules in cmdline.txt
      ansible.builtin.shell: "grep -c 'modules-load=dwc2,g_ether' {{ boot_path }}/cmdline.txt || true"
      register: cmdline_check
      changed_when: false

    - name: Add modules to cmdline.txt
      ansible.builtin.replace:
        path: "{{ boot_path }}/cmdline.txt"
        regexp: '(^.*$)'
        replace: '\\1 modules-load=dwc2,g_ether'
      when: cmdline_check.stdout == "0"
      notify: Reboot Pi

    - name: Persist modules in /etc/modules
      ansible.builtin.blockinfile:
        path: /etc/modules
        block: |
          dwc2
          g_ether
        create: yes

    # --- UBUNTU SPECIFIC NETWORKING ---
    - name: Install NetworkManager (Ubuntu Only)
      ansible.builtin.apt:
        name: network-manager
        state: present
      when: ansible_distribution == 'Ubuntu'

    - name: Configure Netplan to use NetworkManager (Ubuntu Only)
      ansible.builtin.copy:
        dest: /etc/netplan/01-network-manager-all.yaml
        content: |
          network:
            version: 2
            renderer: NetworkManager
            ethernets:
               all-en:
                 match:
                   name: "en*"
                 dhcp4: true
                 optional: true
      when: ansible_distribution == 'Ubuntu'
      notify: Apply Netplan

    # --- CONNECTION SETUP ---
    - name: Create Static Connection for iPad USB
      community.general.nmcli:
        conn_name: "ipad-usb"
        ifname: "usb0"
        type: "ethernet"
        ip4: "{{ otg_ip }}"
        state: present
        autoconnect: yes
      ignore_errors: yes 

    # --- DHCP SERVER ---
    - name: Install dnsmasq
      ansible.builtin.apt:
        name: dnsmasq
        state: present

    - name: Configure dnsmasq for usb0
      ansible.builtin.copy:
        dest: /etc/dnsmasq.d/usb0.conf
        content: |
          interface=usb0
          bind-interfaces
          dhcp-range={{ dhcp_start }},{{ dhcp_end }},255.255.255.0,1h
          # Prevent iPad from using Pi as default gateway (keeps iPad WiFi working)
          dhcp-option=3
        owner: root
        group: root
        mode: '0644'
      notify: Restart dnsmasq

  handlers:
    - name: Apply Netplan
      ansible.builtin.command: netplan apply
      when: ansible_distribution == 'Ubuntu'
    
    - name: Restart dnsmasq
      ansible.builtin.service:
        name: dnsmasq
        state: restarted
        enabled: yes

    - name: Reboot Pi
      ansible.builtin.reboot:
        msg: "Rebooting to activate USB Gadget Mode"