#include <linux/netdevice.h>
 #include <linux/pci.h>
 #include <linux/u64_stats_sync.h>
+
 #include "gve_desc.h"
 
 #ifndef PCI_VENDOR_ID_GOOGLE
 
 #define GVE_DATA_SLOT_ADDR_PAGE_MASK (~(PAGE_SIZE - 1))
 
+/* PTYPEs are always 10 bits. */
+#define GVE_NUM_PTYPES 1024
+
 /* Each slot in the desc ring has a 1:1 mapping to a slot in the data ring */
 struct gve_rx_desc_queue {
        struct gve_rx_desc *desc_ring; /* the descriptor ring */
        u16 rx_buff_ring_entries; /* number of rx_buff descriptors */
 };
 
+struct gve_ptype {
+       u8 l3_type;  /* `gve_l3_type` in gve_adminq.h */
+       u8 l4_type;  /* `gve_l4_type` in gve_adminq.h */
+};
+
+struct gve_ptype_lut {
+       struct gve_ptype ptypes[GVE_NUM_PTYPES];
+};
+
 /* GVE_QUEUE_FORMAT_UNSPECIFIED must be zero since 0 is the default value
  * when the entire configure_device_resources command is zeroed out and the
  * queue_format is not specified.
        u32 adminq_set_driver_parameter_cnt;
        u32 adminq_report_stats_cnt;
        u32 adminq_report_link_speed_cnt;
+       u32 adminq_get_ptype_map_cnt;
 
        /* Global stats */
        u32 interface_up_cnt; /* count of times interface turned up since last reset */
        u64 link_speed;
 
        struct gve_options_dqo_rda options_dqo_rda;
+       struct gve_ptype_lut *ptype_lut_dqo;
 
        enum gve_queue_format queue_format;
 };
 
        priv->adminq_set_driver_parameter_cnt = 0;
        priv->adminq_report_stats_cnt = 0;
        priv->adminq_report_link_speed_cnt = 0;
+       priv->adminq_get_ptype_map_cnt = 0;
 
        /* Setup Admin queue with the device */
        iowrite32be(priv->adminq_bus_addr / PAGE_SIZE,
        case GVE_ADMINQ_REPORT_LINK_SPEED:
                priv->adminq_report_link_speed_cnt++;
                break;
+       case GVE_ADMINQ_GET_PTYPE_MAP:
+               priv->adminq_get_ptype_map_cnt++;
+               break;
        default:
                dev_err(&priv->pdev->dev, "unknown AQ command opcode %d\n", opcode);
        }
  * The caller is also responsible for making sure there are no commands
  * waiting to be executed.
  */
-static int gve_adminq_execute_cmd(struct gve_priv *priv, union gve_adminq_command *cmd_orig)
+static int gve_adminq_execute_cmd(struct gve_priv *priv,
+                                 union gve_adminq_command *cmd_orig)
 {
        u32 tail, head;
        int err;
                          link_speed_region_bus);
        return err;
 }
+
+int gve_adminq_get_ptype_map_dqo(struct gve_priv *priv,
+                                struct gve_ptype_lut *ptype_lut)
+{
+       struct gve_ptype_map *ptype_map;
+       union gve_adminq_command cmd;
+       dma_addr_t ptype_map_bus;
+       int err = 0;
+       int i;
+
+       memset(&cmd, 0, sizeof(cmd));
+       ptype_map = dma_alloc_coherent(&priv->pdev->dev, sizeof(*ptype_map),
+                                      &ptype_map_bus, GFP_KERNEL);
+       if (!ptype_map)
+               return -ENOMEM;
+
+       cmd.opcode = cpu_to_be32(GVE_ADMINQ_GET_PTYPE_MAP);
+       cmd.get_ptype_map = (struct gve_adminq_get_ptype_map) {
+               .ptype_map_len = cpu_to_be64(sizeof(*ptype_map)),
+               .ptype_map_addr = cpu_to_be64(ptype_map_bus),
+       };
+
+       err = gve_adminq_execute_cmd(priv, &cmd);
+       if (err)
+               goto err;
+
+       /* Populate ptype_lut. */
+       for (i = 0; i < GVE_NUM_PTYPES; i++) {
+               ptype_lut->ptypes[i].l3_type =
+                       ptype_map->ptypes[i].l3_type;
+               ptype_lut->ptypes[i].l4_type =
+                       ptype_map->ptypes[i].l4_type;
+       }
+err:
+       dma_free_coherent(&priv->pdev->dev, sizeof(*ptype_map), ptype_map,
+                         ptype_map_bus);
+       return err;
+}
 
        GVE_ADMINQ_DECONFIGURE_DEVICE_RESOURCES = 0x9,
        GVE_ADMINQ_SET_DRIVER_PARAMETER         = 0xB,
        GVE_ADMINQ_REPORT_STATS                 = 0xC,
-       GVE_ADMINQ_REPORT_LINK_SPEED    = 0xD
+       GVE_ADMINQ_REPORT_LINK_SPEED            = 0xD,
+       GVE_ADMINQ_GET_PTYPE_MAP                = 0xE,
 };
 
 /* Admin queue status codes */
        RX_DROPS_INVALID_CHECKSUM       = 68,
 };
 
+enum gve_l3_type {
+       /* Must be zero so zero initialized LUT is unknown. */
+       GVE_L3_TYPE_UNKNOWN = 0,
+       GVE_L3_TYPE_OTHER,
+       GVE_L3_TYPE_IPV4,
+       GVE_L3_TYPE_IPV6,
+};
+
+enum gve_l4_type {
+       /* Must be zero so zero initialized LUT is unknown. */
+       GVE_L4_TYPE_UNKNOWN = 0,
+       GVE_L4_TYPE_OTHER,
+       GVE_L4_TYPE_TCP,
+       GVE_L4_TYPE_UDP,
+       GVE_L4_TYPE_ICMP,
+       GVE_L4_TYPE_SCTP,
+};
+
+/* These are control path types for PTYPE which are the same as the data path
+ * types.
+ */
+struct gve_ptype_entry {
+       u8 l3_type;
+       u8 l4_type;
+};
+
+struct gve_ptype_map {
+       struct gve_ptype_entry ptypes[1 << 10]; /* PTYPES are always 10 bits. */
+};
+
+struct gve_adminq_get_ptype_map {
+       __be64 ptype_map_len;
+       __be64 ptype_map_addr;
+};
+
 union gve_adminq_command {
        struct {
                __be32 opcode;
                        struct gve_adminq_set_driver_parameter set_driver_param;
                        struct gve_adminq_report_stats report_stats;
                        struct gve_adminq_report_link_speed report_link_speed;
+                       struct gve_adminq_get_ptype_map get_ptype_map;
                };
        };
        u8 reserved[64];
 int gve_adminq_report_stats(struct gve_priv *priv, u64 stats_report_len,
                            dma_addr_t stats_report_addr, u64 interval);
 int gve_adminq_report_link_speed(struct gve_priv *priv);
+
+struct gve_ptype_lut;
+int gve_adminq_get_ptype_map_dqo(struct gve_priv *priv,
+                                struct gve_ptype_lut *ptype_lut);
+
 #endif /* _GVE_ADMINQ_H */
 
                err = -ENXIO;
                goto abort_with_stats_report;
        }
+
+       if (priv->queue_format == GVE_DQO_RDA_FORMAT) {
+               priv->ptype_lut_dqo = kvzalloc(sizeof(*priv->ptype_lut_dqo),
+                                              GFP_KERNEL);
+               if (!priv->ptype_lut_dqo) {
+                       err = -ENOMEM;
+                       goto abort_with_stats_report;
+               }
+               err = gve_adminq_get_ptype_map_dqo(priv, priv->ptype_lut_dqo);
+               if (err) {
+                       dev_err(&priv->pdev->dev,
+                               "Failed to get ptype map: err=%d\n", err);
+                       goto abort_with_ptype_lut;
+               }
+       }
+
        err = gve_adminq_report_stats(priv, priv->stats_report_len,
                                      priv->stats_report_bus,
                                      GVE_STATS_REPORT_TIMER_PERIOD);
                        "Failed to report stats: err=%d\n", err);
        gve_set_device_resources_ok(priv);
        return 0;
+
+abort_with_ptype_lut:
+       kvfree(priv->ptype_lut_dqo);
+       priv->ptype_lut_dqo = NULL;
 abort_with_stats_report:
        gve_free_stats_report(priv);
 abort_with_ntfy_blocks:
        gve_free_notify_blocks(priv);
 abort_with_counter:
        gve_free_counter_array(priv);
+
        return err;
 }
 
                        gve_trigger_reset(priv);
                }
        }
+
+       kvfree(priv->ptype_lut_dqo);
+       priv->ptype_lut_dqo = NULL;
+
        gve_free_counter_array(priv);
        gve_free_notify_blocks(priv);
        gve_free_stats_report(priv);