static void gem_write(void *opaque, hwaddr offset, uint64_t val,
        unsigned size)
{
    CadenceGEMState *s = (CadenceGEMState *)opaque;
    uint32_t readonly;
    int i;

    DB_PRINT("offset: 0x%04x write: 0x%08x ", (unsigned)offset, (unsigned)val);
    offset >>= 2;

    /* Squash bits which are read only in write value */
    val &= ~(s->regs_ro[offset]);
    /* Preserve (only) bits which are read only and wtc in register */
    readonly = s->regs[offset] & (s->regs_ro[offset] | s->regs_w1c[offset]);

    /* Copy register write to backing store */
    s->regs[offset] = (val & ~s->regs_w1c[offset]) | readonly;

    /* do w1c */
    s->regs[offset] &= ~(s->regs_w1c[offset] & val);

    /* Handle register write side effects */
    switch (offset) {
    case GEM_NWCTRL:
        if (val & GEM_NWCTRL_RXENA) {
            for (i = 0; i < s->num_priority_queues; ++i) {
                gem_get_rx_desc(s, i);
            }
        }
        if (val & GEM_NWCTRL_TXSTART) {
            gem_transmit(s);
        }
        if (!(val & GEM_NWCTRL_TXENA)) {
            /* Reset to start of Q when transmit disabled. */
            for (i = 0; i < s->num_priority_queues; i++) {
                s->tx_desc_addr[i] = gem_get_tx_queue_base_addr(s, i);
            }
        }
        if (gem_can_receive(qemu_get_queue(s->nic))) {
            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        }
        break;

    case GEM_TXSTATUS:
        gem_update_int_status(s);
        break;
    case GEM_RXQBASE:
        s->rx_desc_addr[0] = val;
        break;
    case GEM_RECEIVE_Q1_PTR ... GEM_RECEIVE_Q7_PTR:
        s->rx_desc_addr[offset - GEM_RECEIVE_Q1_PTR + 1] = val;
        break;
    case GEM_TXQBASE:
        s->tx_desc_addr[0] = val;
        break;
    case GEM_TRANSMIT_Q1_PTR ... GEM_TRANSMIT_Q7_PTR:
        s->tx_desc_addr[offset - GEM_TRANSMIT_Q1_PTR + 1] = val;
        break;
    case GEM_RXSTATUS:
        gem_update_int_status(s);
        break;
    case GEM_IER:
        s->regs[GEM_IMR] &= ~val;
        gem_update_int_status(s);
        break;
    case GEM_JUMBO_MAX_LEN:
        s->regs[GEM_JUMBO_MAX_LEN] = val & MAX_JUMBO_FRAME_SIZE_MASK;
        break;
    case GEM_INT_Q1_ENABLE ... GEM_INT_Q7_ENABLE:
        s->regs[GEM_INT_Q1_MASK + offset - GEM_INT_Q1_ENABLE] &= ~val;
        gem_update_int_status(s);
        break;
    case GEM_IDR:
        s->regs[GEM_IMR] |= val;
        gem_update_int_status(s);
        break;
    case GEM_INT_Q1_DISABLE ... GEM_INT_Q7_DISABLE:
        s->regs[GEM_INT_Q1_MASK + offset - GEM_INT_Q1_DISABLE] |= val;
        gem_update_int_status(s);
        break;
    case GEM_SPADDR1LO:
    case GEM_SPADDR2LO:
    case GEM_SPADDR3LO:
    case GEM_SPADDR4LO:
        s->sar_active[(offset - GEM_SPADDR1LO) / 2] = false;
        break;
    case GEM_SPADDR1HI:
    case GEM_SPADDR2HI:
    case GEM_SPADDR3HI:
    case GEM_SPADDR4HI:
        s->sar_active[(offset - GEM_SPADDR1HI) / 2] = true;
        break;
    case GEM_PHYMNTNC:
        if (val & GEM_PHYMNTNC_OP_W) {
            uint32_t phy_addr, reg_num;

            phy_addr = (val & GEM_PHYMNTNC_ADDR) >> GEM_PHYMNTNC_ADDR_SHFT;
            if (phy_addr == s->phy_addr) {
                reg_num = (val & GEM_PHYMNTNC_REG) >> GEM_PHYMNTNC_REG_SHIFT;
                gem_phy_write(s, reg_num, val);
            }
        }
        break;
    }

    DB_PRINT("newval: 0x%08x\n", s->regs[offset]);
}