aboutsummaryrefslogtreecommitdiff
path: root/hw/cdrom.c
blob: 3b99535dcebc3e005811ce1500cbd2d2e2bfb523 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
 * QEMU ATAPI CD-ROM Emulator
 *
 * Copyright (c) 2006 Fabrice Bellard
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* ??? Most of the ATAPI emulation is still in ide.c.  It should be moved
   here.  */

#include "qemu-common.h"
#include "scsi.h"

static void lba_to_msf(uint8_t *buf, int lba)
{
    lba += 150;
    buf[0] = (lba / 75) / 60;
    buf[1] = (lba / 75) % 60;
    buf[2] = lba % 75;
}

/* same toc as bochs. Return -1 if error or the toc length */
/* XXX: check this */
int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track)
{
    uint8_t *q;
    int len;

    if (start_track > 1 && start_track != 0xaa)
        return -1;
    q = buf + 2;
    *q++ = 1; /* first session */
    *q++ = 1; /* last session */
    if (start_track <= 1) {
        *q++ = 0; /* reserved */
        *q++ = 0x14; /* ADR, control */
        *q++ = 1;    /* track number */
        *q++ = 0; /* reserved */
        if (msf) {
            *q++ = 0; /* reserved */
            lba_to_msf(q, 0);
            q += 3;
        } else {
            /* sector 0 */
            cpu_to_be32wu((uint32_t *)q, 0);
            q += 4;
        }
    }
    /* lead out track */
    *q++ = 0; /* reserved */
    *q++ = 0x16; /* ADR, control */
    *q++ = 0xaa; /* track number */
    *q++ = 0; /* reserved */
    if (msf) {
        *q++ = 0; /* reserved */
        lba_to_msf(q, nb_sectors);
        q += 3;
    } else {
        cpu_to_be32wu((uint32_t *)q, nb_sectors);
        q += 4;
    }
    len = q - buf;
    cpu_to_be16wu((uint16_t *)buf, len - 2);
    return len;
}

/* mostly same info as PearPc */
int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num)
{
    uint8_t *q;
    int len;

    q = buf + 2;
    *q++ = 1; /* first session */
    *q++ = 1; /* last session */

    *q++ = 1; /* session number */
    *q++ = 0x14; /* data track */
    *q++ = 0; /* track number */
    *q++ = 0xa0; /* lead-in */
    *q++ = 0; /* min */
    *q++ = 0; /* sec */
    *q++ = 0; /* frame */
    *q++ = 0;
    *q++ = 1; /* first track */
    *q++ = 0x00; /* disk type */
    *q++ = 0x00;

    *q++ = 1; /* session number */
    *q++ = 0x14; /* data track */
    *q++ = 0; /* track number */
    *q++ = 0xa1;
    *q++ = 0; /* min */
    *q++ = 0; /* sec */
    *q++ = 0; /* frame */
    *q++ = 0;
    *q++ = 1; /* last track */
    *q++ = 0x00;
    *q++ = 0x00;

    *q++ = 1; /* session number */
    *q++ = 0x14; /* data track */
    *q++ = 0; /* track number */
    *q++ = 0xa2; /* lead-out */
    *q++ = 0; /* min */
    *q++ = 0; /* sec */
    *q++ = 0; /* frame */
    if (msf) {
        *q++ = 0; /* reserved */
        lba_to_msf(q, nb_sectors);
        q += 3;
    } else {
        cpu_to_be32wu((uint32_t *)q, nb_sectors);
        q += 4;
    }

    *q++ = 1; /* session number */
    *q++ = 0x14; /* ADR, control */
    *q++ = 0;    /* track number */
    *q++ = 1;    /* point */
    *q++ = 0; /* min */
    *q++ = 0; /* sec */
    *q++ = 0; /* frame */
    if (msf) {
        *q++ = 0;
        lba_to_msf(q, 0);
        q += 3;
    } else {
        *q++ = 0;
        *q++ = 0;
        *q++ = 0;
        *q++ = 0;
    }

    len = q - buf;
    cpu_to_be16wu((uint16_t *)buf, len - 2);
    return len;
}
/* Receive Process State */ #define DMA_STATUS_RS_SHIFT 17 #define DMA_STATUS_NIS 0x00010000 /* Normal Interrupt Summary */ #define DMA_STATUS_AIS 0x00008000 /* Abnormal Interrupt Summary */ #define DMA_STATUS_ERI 0x00004000 /* Early Receive Interrupt */ #define DMA_STATUS_FBI 0x00002000 /* Fatal Bus Error Interrupt */ #define DMA_STATUS_ETI 0x00000400 /* Early Transmit Interrupt */ #define DMA_STATUS_RWT 0x00000200 /* Receive Watchdog Timeout */ #define DMA_STATUS_RPS 0x00000100 /* Receive Process Stopped */ #define DMA_STATUS_RU 0x00000080 /* Receive Buffer Unavailable */ #define DMA_STATUS_RI 0x00000040 /* Receive Interrupt */ #define DMA_STATUS_UNF 0x00000020 /* Transmit Underflow */ #define DMA_STATUS_OVF 0x00000010 /* Receive Overflow */ #define DMA_STATUS_TJT 0x00000008 /* Transmit Jabber Timeout */ #define DMA_STATUS_TU 0x00000004 /* Transmit Buffer Unavailable */ #define DMA_STATUS_TPS 0x00000002 /* Transmit Process Stopped */ #define DMA_STATUS_TI 0x00000001 /* Transmit Interrupt */ /* DMA Control register defines */ #define DMA_CONTROL_ST 0x00002000 /* Start/Stop Transmission */ #define DMA_CONTROL_SR 0x00000002 /* Start/Stop Receive */ #define DMA_CONTROL_DFF 0x01000000 /* Disable flush of rx frames */ struct desc { uint32_t ctl_stat; uint16_t buffer1_size; uint16_t buffer2_size; uint32_t buffer1_addr; uint32_t buffer2_addr; uint32_t ext_stat; uint32_t res[3]; }; #define R_MAX 0x400 typedef struct RxTxStats { uint64_t rx_bytes; uint64_t tx_bytes; uint64_t rx; uint64_t rx_bcast; uint64_t rx_mcast; } RxTxStats; #define TYPE_XGMAC "xgmac" #define XGMAC(obj) OBJECT_CHECK(XgmacState, (obj), TYPE_XGMAC) typedef struct XgmacState { SysBusDevice parent_obj; MemoryRegion iomem; qemu_irq sbd_irq; qemu_irq pmt_irq; qemu_irq mci_irq; NICState *nic; NICConf conf; struct RxTxStats stats; uint32_t regs[R_MAX]; } XgmacState; static const VMStateDescription vmstate_rxtx_stats = { .name = "xgmac_stats", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_UINT64(rx_bytes, RxTxStats), VMSTATE_UINT64(tx_bytes, RxTxStats), VMSTATE_UINT64(rx, RxTxStats), VMSTATE_UINT64(rx_bcast, RxTxStats), VMSTATE_UINT64(rx_mcast, RxTxStats), VMSTATE_END_OF_LIST() } }; static const VMStateDescription vmstate_xgmac = { .name = "xgmac", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_STRUCT(stats, XgmacState, 0, vmstate_rxtx_stats, RxTxStats), VMSTATE_UINT32_ARRAY(regs, XgmacState, R_MAX), VMSTATE_END_OF_LIST() } }; static void xgmac_read_desc(XgmacState *s, struct desc *d, int rx) { uint32_t addr = rx ? s->regs[DMA_CUR_RX_DESC_ADDR] : s->regs[DMA_CUR_TX_DESC_ADDR]; cpu_physical_memory_read(addr, d, sizeof(*d)); } static void xgmac_write_desc(XgmacState *s, struct desc *d, int rx) { int reg = rx ? DMA_CUR_RX_DESC_ADDR : DMA_CUR_TX_DESC_ADDR; uint32_t addr = s->regs[reg]; if (!rx && (d->ctl_stat & 0x00200000)) { s->regs[reg] = s->regs[DMA_TX_BASE_ADDR]; } else if (rx && (d->buffer1_size & 0x8000)) { s->regs[reg] = s->regs[DMA_RCV_BASE_ADDR]; } else { s->regs[reg] += sizeof(*d); } cpu_physical_memory_write(addr, d, sizeof(*d)); } static void xgmac_enet_send(XgmacState *s) { struct desc bd; int frame_size; int len; uint8_t frame[8192]; uint8_t *ptr; ptr = frame; frame_size = 0; while (1) { xgmac_read_desc(s, &bd, 0); if ((bd.ctl_stat & 0x80000000) == 0) { /* Run out of descriptors to transmit. */ break; } len = (bd.buffer1_size & 0xfff) + (bd.buffer2_size & 0xfff); if ((bd.buffer1_size & 0xfff) > 2048) { DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " "xgmac buffer 1 len on send > 2048 (0x%x)\n", __func__, bd.buffer1_size & 0xfff); } if ((bd.buffer2_size & 0xfff) != 0) { DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " "xgmac buffer 2 len on send != 0 (0x%x)\n", __func__, bd.buffer2_size & 0xfff); } if (len >= sizeof(frame)) { DEBUGF_BRK("qemu:%s: buffer overflow %d read into %zu " "buffer\n" , __func__, len, sizeof(frame)); DEBUGF_BRK("qemu:%s: buffer1.size=%d; buffer2.size=%d\n", __func__, bd.buffer1_size, bd.buffer2_size); } cpu_physical_memory_read(bd.buffer1_addr, ptr, len); ptr += len; frame_size += len; if (bd.ctl_stat & 0x20000000) { /* Last buffer in frame. */ qemu_send_packet(qemu_get_queue(s->nic), frame, len); ptr = frame; frame_size = 0; s->regs[DMA_STATUS] |= DMA_STATUS_TI | DMA_STATUS_NIS; } bd.ctl_stat &= ~0x80000000; /* Write back the modified descriptor. */ xgmac_write_desc(s, &bd, 0); } } static void enet_update_irq(XgmacState *s) { int stat = s->regs[DMA_STATUS] & s->regs[DMA_INTR_ENA]; qemu_set_irq(s->sbd_irq, !!stat); } static uint64_t enet_read(void *opaque, hwaddr addr, unsigned size) { XgmacState *s = opaque; uint64_t r = 0; addr >>= 2; switch (addr) { case XGMAC_VERSION: r = 0x1012; break; default: if (addr < ARRAY_SIZE(s->regs)) { r = s->regs[addr]; } break; } return r; } static void enet_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { XgmacState *s = opaque; addr >>= 2; switch (addr) { case DMA_BUS_MODE: s->regs[DMA_BUS_MODE] = value & ~0x1; break; case DMA_XMT_POLL_DEMAND: xgmac_enet_send(s); break; case DMA_STATUS: s->regs[DMA_STATUS] = s->regs[DMA_STATUS] & ~value; break; case DMA_RCV_BASE_ADDR: s->regs[DMA_RCV_BASE_ADDR] = s->regs[DMA_CUR_RX_DESC_ADDR] = value; break; case DMA_TX_BASE_ADDR: s->regs[DMA_TX_BASE_ADDR] = s->regs[DMA_CUR_TX_DESC_ADDR] = value; break; default: if (addr < ARRAY_SIZE(s->regs)) { s->regs[addr] = value; } break; } enet_update_irq(s); } static const MemoryRegionOps enet_mem_ops = { .read = enet_read, .write = enet_write, .endianness = DEVICE_LITTLE_ENDIAN, }; static int eth_can_rx(XgmacState *s) { /* RX enabled? */ return s->regs[DMA_CONTROL] & DMA_CONTROL_SR; } static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size) { XgmacState *s = qemu_get_nic_opaque(nc); static const unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; int unicast, broadcast, multicast; struct desc bd; ssize_t ret; if (!eth_can_rx(s)) { return -1; } unicast = ~buf[0] & 0x1; broadcast = memcmp(buf, sa_bcast, 6) == 0; multicast = !unicast && !broadcast; if (size < 12) { s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; ret = -1; goto out; } xgmac_read_desc(s, &bd, 1); if ((bd.ctl_stat & 0x80000000) == 0) { s->regs[DMA_STATUS] |= DMA_STATUS_RU | DMA_STATUS_AIS; ret = size; goto out; } cpu_physical_memory_write(bd.buffer1_addr, buf, size); /* Add in the 4 bytes for crc (the real hw returns length incl crc) */ size += 4; bd.ctl_stat = (size << 16) | 0x300; xgmac_write_desc(s, &bd, 1); s->stats.rx_bytes += size; s->stats.rx++; if (multicast) { s->stats.rx_mcast++; } else if (broadcast) { s->stats.rx_bcast++; } s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; ret = size; out: enet_update_irq(s); return ret; } static NetClientInfo net_xgmac_enet_info = { .type = NET_CLIENT_DRIVER_NIC, .size = sizeof(NICState), .receive = eth_rx, }; static void xgmac_enet_realize(DeviceState *dev, Error **errp) { SysBusDevice *sbd = SYS_BUS_DEVICE(dev); XgmacState *s = XGMAC(dev); memory_region_init_io(&s->iomem, OBJECT(s), &enet_mem_ops, s, "xgmac", 0x1000); sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->sbd_irq); sysbus_init_irq(sbd, &s->pmt_irq); sysbus_init_irq(sbd, &s->mci_irq); qemu_macaddr_default_if_unset(&s->conf.macaddr); s->nic = qemu_new_nic(&net_xgmac_enet_info, &s->conf, object_get_typename(OBJECT(dev)), dev->id, s); qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); s->regs[XGMAC_ADDR_HIGH(0)] = (s->conf.macaddr.a[5] << 8) | s->conf.macaddr.a[4]; s->regs[XGMAC_ADDR_LOW(0)] = (s->conf.macaddr.a[3] << 24) | (s->conf.macaddr.a[2] << 16) | (s->conf.macaddr.a[1] << 8) | s->conf.macaddr.a[0]; } static Property xgmac_properties[] = { DEFINE_NIC_PROPERTIES(XgmacState, conf), DEFINE_PROP_END_OF_LIST(), }; static void xgmac_enet_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = xgmac_enet_realize; dc->vmsd = &vmstate_xgmac; dc->props = xgmac_properties; } static const TypeInfo xgmac_enet_info = { .name = TYPE_XGMAC, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XgmacState), .class_init = xgmac_enet_class_init, }; static void xgmac_enet_register_types(void) { type_register_static(&xgmac_enet_info); } type_init(xgmac_enet_register_types)