1/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12#include "migration/qemu-file.h"
13#include "sysemu/char.h"
14#include "hw/android/goldfish/device.h"
15#include "hw/android/goldfish/vmem.h"
16#include "hw/hw.h"
17
18enum {
19    TTY_PUT_CHAR       = 0x00,
20    TTY_BYTES_READY    = 0x04,
21    TTY_CMD            = 0x08,
22
23    TTY_DATA_PTR       = 0x10,
24    TTY_DATA_LEN       = 0x14,
25    TTY_DATA_PTR_HIGH  = 0x18,
26
27    TTY_CMD_INT_DISABLE    = 0,
28    TTY_CMD_INT_ENABLE     = 1,
29    TTY_CMD_WRITE_BUFFER   = 2,
30    TTY_CMD_READ_BUFFER    = 3,
31};
32
33struct tty_state {
34    struct goldfish_device dev;
35    CharDriverState *cs;
36    uint64_t ptr;
37    uint32_t ptr_len;
38    uint32_t ready;
39    uint8_t data[128];
40    uint32_t data_count;
41};
42
43#define  GOLDFISH_TTY_SAVE_VERSION  2
44
45static void  goldfish_tty_save(QEMUFile*  f, void*  opaque)
46{
47    struct tty_state*  s = opaque;
48
49    qemu_put_be64( f, s->ptr );
50    qemu_put_be32( f, s->ptr_len );
51    qemu_put_byte( f, s->ready );
52    qemu_put_byte( f, s->data_count );
53    qemu_put_buffer( f, s->data, s->data_count );
54}
55
56static int  goldfish_tty_load(QEMUFile*  f, void*  opaque, int  version_id)
57{
58    struct tty_state*  s = opaque;
59
60    if ((version_id != GOLDFISH_TTY_SAVE_VERSION) &&
61        (version_id != (GOLDFISH_TTY_SAVE_VERSION - 1))) {
62        return -1;
63    }
64    if (version_id == (GOLDFISH_TTY_SAVE_VERSION - 1)) {
65        s->ptr    = (uint64_t)qemu_get_be32(f);
66    } else {
67        s->ptr    = qemu_get_be64(f);
68    }
69    s->ptr_len    = qemu_get_be32(f);
70    s->ready      = qemu_get_byte(f);
71    s->data_count = qemu_get_byte(f);
72    qemu_get_buffer(f, s->data, s->data_count);
73
74    return 0;
75}
76
77static uint32_t goldfish_tty_read(void *opaque, hwaddr offset)
78{
79    struct tty_state *s = (struct tty_state *)opaque;
80
81    //printf("goldfish_tty_read %x %x\n", offset, size);
82
83    switch (offset) {
84        case TTY_BYTES_READY:
85            return s->data_count;
86    default:
87        cpu_abort (cpu_single_env, "goldfish_tty_read: Bad offset %x\n", offset);
88        return 0;
89    }
90}
91
92static void goldfish_tty_write(void *opaque, hwaddr offset, uint32_t value)
93{
94    struct tty_state *s = (struct tty_state *)opaque;
95
96    //printf("goldfish_tty_write %x %x %x\n", offset, value, size);
97
98    switch(offset) {
99        case TTY_PUT_CHAR: {
100            uint8_t ch = value;
101            if(s->cs)
102                qemu_chr_write(s->cs, &ch, 1);
103        } break;
104
105        case TTY_CMD:
106            switch(value) {
107                case TTY_CMD_INT_DISABLE:
108                    if(s->ready) {
109                        if(s->data_count > 0)
110                            goldfish_device_set_irq(&s->dev, 0, 0);
111                        s->ready = 0;
112                    }
113                    break;
114
115                case TTY_CMD_INT_ENABLE:
116                    if(!s->ready) {
117                        if(s->data_count > 0)
118                            goldfish_device_set_irq(&s->dev, 0, 1);
119                        s->ready = 1;
120                    }
121                    break;
122
123                case TTY_CMD_WRITE_BUFFER:
124                    if(s->cs) {
125                        int len;
126                        target_ulong  buf;
127
128                        buf = s->ptr;
129                        len = s->ptr_len;
130
131                        while (len) {
132                            char   temp[64];
133                            int    to_write = sizeof(temp);
134                            if (to_write > len)
135                                to_write = len;
136
137                            safe_memory_rw_debug(current_cpu, buf, (uint8_t*)temp, to_write, 0);
138                            qemu_chr_write(s->cs, (const uint8_t*)temp, to_write);
139                            buf += to_write;
140                            len -= to_write;
141                        }
142                        //printf("goldfish_tty_write: got %d bytes from %llx\n", s->ptr_len, (unsigned long long)s->ptr);
143                    }
144                    break;
145
146                case TTY_CMD_READ_BUFFER:
147                    if(s->ptr_len > s->data_count)
148                        cpu_abort (cpu_single_env, "goldfish_tty_write: reading more data than available %d %d\n", s->ptr_len, s->data_count);
149                    safe_memory_rw_debug(current_cpu, s->ptr, s->data, s->ptr_len,1);
150                    //printf("goldfish_tty_write: read %d bytes to %llx\n", s->ptr_len, (unsigned long long)s->ptr);
151                    if(s->data_count > s->ptr_len)
152                        memmove(s->data, s->data + s->ptr_len, s->data_count - s->ptr_len);
153                    s->data_count -= s->ptr_len;
154                    if(s->data_count == 0 && s->ready)
155                        goldfish_device_set_irq(&s->dev, 0, 0);
156                    break;
157
158                default:
159                    cpu_abort (cpu_single_env, "goldfish_tty_write: Bad command %x\n", value);
160            };
161            break;
162
163        case TTY_DATA_PTR:
164            uint64_set_low(&s->ptr, value);
165            break;
166
167        case TTY_DATA_PTR_HIGH:
168            uint64_set_high(&s->ptr, value);
169            break;
170
171        case TTY_DATA_LEN:
172            s->ptr_len = value;
173            break;
174
175        default:
176            cpu_abort (cpu_single_env, "goldfish_tty_write: Bad offset %x\n", offset);
177    }
178}
179
180static int tty_can_receive(void *opaque)
181{
182    struct tty_state *s = opaque;
183
184    return (sizeof(s->data) - s->data_count);
185}
186
187static void tty_receive(void *opaque, const uint8_t *buf, int size)
188{
189    struct tty_state *s = opaque;
190
191    memcpy(s->data + s->data_count, buf, size);
192    s->data_count += size;
193    if(s->data_count > 0 && s->ready)
194        goldfish_device_set_irq(&s->dev, 0, 1);
195}
196
197static CPUReadMemoryFunc *goldfish_tty_readfn[] = {
198    goldfish_tty_read,
199    goldfish_tty_read,
200    goldfish_tty_read
201};
202
203static CPUWriteMemoryFunc *goldfish_tty_writefn[] = {
204    goldfish_tty_write,
205    goldfish_tty_write,
206    goldfish_tty_write
207};
208
209int goldfish_tty_add(CharDriverState *cs, int id, uint32_t base, int irq)
210{
211    int ret;
212    struct tty_state *s;
213    static int  instance_id = 0;
214
215    s = g_malloc0(sizeof(*s));
216    s->dev.name = "goldfish_tty";
217    s->dev.id = id;
218    s->dev.base = base;
219    s->dev.size = 0x1000;
220    s->dev.irq = irq;
221    s->dev.irq_count = 1;
222    s->cs = cs;
223
224    if(cs) {
225        qemu_chr_add_handlers(cs, tty_can_receive, tty_receive, NULL, s);
226    }
227
228    ret = goldfish_device_add(&s->dev, goldfish_tty_readfn, goldfish_tty_writefn, s);
229    if(ret) {
230        g_free(s);
231    } else {
232        register_savevm(NULL,
233                        "goldfish_tty",
234                        instance_id++,
235                        GOLDFISH_TTY_SAVE_VERSION,
236                        goldfish_tty_save,
237                        goldfish_tty_load,
238                        s);
239    }
240    return ret;
241}
242
243