symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/soc_dma.c
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 /*
       
     2  * On-chip DMA controller framework.
       
     3  *
       
     4  * Copyright (C) 2008 Nokia Corporation
       
     5  * Written by Andrzej Zaborowski <andrew@openedhand.com>
       
     6  *
       
     7  * This program is free software; you can redistribute it and/or
       
     8  * modify it under the terms of the GNU General Public License as
       
     9  * published by the Free Software Foundation; either version 2 or
       
    10  * (at your option) version 3 of the License.
       
    11  *
       
    12  * This program is distributed in the hope that it will be useful,
       
    13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    15  * GNU General Public License for more details.
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License
       
    18  * along with this program; if not, write to the Free Software
       
    19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
       
    20  * MA 02111-1307 USA
       
    21  */
       
    22 #include "qemu-common.h"
       
    23 #include "qemu-timer.h"
       
    24 #include "soc_dma.h"
       
    25 
       
    26 static void transfer_mem2mem(struct soc_dma_ch_s *ch)
       
    27 {
       
    28     memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
       
    29     ch->paddr[0] += ch->bytes;
       
    30     ch->paddr[1] += ch->bytes;
       
    31 }
       
    32 
       
    33 static void transfer_mem2fifo(struct soc_dma_ch_s *ch)
       
    34 {
       
    35     ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
       
    36     ch->paddr[0] += ch->bytes;
       
    37 }
       
    38 
       
    39 static void transfer_fifo2mem(struct soc_dma_ch_s *ch)
       
    40 {
       
    41     ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
       
    42     ch->paddr[1] += ch->bytes;
       
    43 }
       
    44 
       
    45 /* This is further optimisable but isn't very important because often
       
    46  * DMA peripherals forbid this kind of transfers and even when they don't,
       
    47  * oprating systems may not need to use them.  */
       
    48 static void *fifo_buf;
       
    49 static int fifo_size;
       
    50 static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
       
    51 {
       
    52     if (ch->bytes > fifo_size)
       
    53         fifo_buf = qemu_realloc(fifo_buf, fifo_size = ch->bytes);
       
    54 
       
    55     /* Implement as transfer_fifo2linear + transfer_linear2fifo.  */
       
    56     ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
       
    57     ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
       
    58 }
       
    59 
       
    60 struct dma_s {
       
    61     struct soc_dma_s soc;
       
    62     int chnum;
       
    63     uint64_t ch_enable_mask;
       
    64     int64_t channel_freq;
       
    65     int enabled_count;
       
    66 
       
    67     struct memmap_entry_s {
       
    68         enum soc_dma_port_type type;
       
    69         target_phys_addr_t addr;
       
    70         union {
       
    71            struct {
       
    72                void *opaque;
       
    73                soc_dma_io_t fn;
       
    74                int out;
       
    75            } fifo;
       
    76            struct {
       
    77                void *base;
       
    78                size_t size;
       
    79            } mem;
       
    80         } u;
       
    81     } *memmap;
       
    82     int memmap_size;
       
    83 
       
    84     struct soc_dma_ch_s ch[0];
       
    85 };
       
    86 
       
    87 static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
       
    88 {
       
    89     int64_t now = qemu_get_clock(vm_clock);
       
    90     struct dma_s *dma = (struct dma_s *) ch->dma;
       
    91 
       
    92     qemu_mod_timer(ch->timer, now + delay_bytes / dma->channel_freq);
       
    93 }
       
    94 
       
    95 static void soc_dma_ch_run(void *opaque)
       
    96 {
       
    97     struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
       
    98 
       
    99     ch->running = 1;
       
   100     ch->dma->setup_fn(ch);
       
   101     ch->transfer_fn(ch);
       
   102     ch->running = 0;
       
   103 
       
   104     if (ch->enable)
       
   105         soc_dma_ch_schedule(ch, ch->bytes);
       
   106     ch->bytes = 0;
       
   107 }
       
   108 
       
   109 static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
       
   110                 target_phys_addr_t addr)
       
   111 {
       
   112     struct memmap_entry_s *lo;
       
   113     int hi;
       
   114 
       
   115     lo = dma->memmap;
       
   116     hi = dma->memmap_size;
       
   117 
       
   118     while (hi > 1) {
       
   119         hi /= 2;
       
   120         if (lo[hi].addr <= addr)
       
   121             lo += hi;
       
   122     }
       
   123 
       
   124     return lo;
       
   125 }
       
   126 
       
   127 static inline enum soc_dma_port_type soc_dma_ch_update_type(
       
   128                 struct soc_dma_ch_s *ch, int port)
       
   129 {
       
   130     struct dma_s *dma = (struct dma_s *) ch->dma;
       
   131     struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
       
   132 
       
   133     if (entry->type == soc_dma_port_fifo) {
       
   134         while (entry < dma->memmap + dma->memmap_size &&
       
   135                         entry->u.fifo.out != port)
       
   136             entry ++;
       
   137         if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
       
   138             return soc_dma_port_other;
       
   139 
       
   140         if (ch->type[port] != soc_dma_access_const)
       
   141             return soc_dma_port_other;
       
   142 
       
   143         ch->io_fn[port] = entry->u.fifo.fn;
       
   144         ch->io_opaque[port] = entry->u.fifo.opaque;
       
   145         return soc_dma_port_fifo;
       
   146     } else if (entry->type == soc_dma_port_mem) {
       
   147         if (entry->addr > ch->vaddr[port] ||
       
   148                         entry->addr + entry->u.mem.size <= ch->vaddr[port])
       
   149             return soc_dma_port_other;
       
   150 
       
   151         /* TODO: support constant memory address for source port as used for
       
   152          * drawing solid rectangles by PalmOS(R).  */
       
   153         if (ch->type[port] != soc_dma_access_const)
       
   154             return soc_dma_port_other;
       
   155 
       
   156         ch->paddr[port] = (uint8_t *) entry->u.mem.base +
       
   157                 (ch->vaddr[port] - entry->addr);
       
   158         /* TODO: save bytes left to the end of the mapping somewhere so we
       
   159          * can check we're not reading beyond it.  */
       
   160         return soc_dma_port_mem;
       
   161     } else
       
   162         return soc_dma_port_other;
       
   163 }
       
   164 
       
   165 void soc_dma_ch_update(struct soc_dma_ch_s *ch)
       
   166 {
       
   167     enum soc_dma_port_type src, dst;
       
   168 
       
   169     src = soc_dma_ch_update_type(ch, 0);
       
   170     if (src == soc_dma_port_other) {
       
   171         ch->update = 0;
       
   172         ch->transfer_fn = ch->dma->transfer_fn;
       
   173         return;
       
   174     }
       
   175     dst = soc_dma_ch_update_type(ch, 1);
       
   176 
       
   177     /* TODO: use src and dst as array indices.  */
       
   178     if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
       
   179         ch->transfer_fn = transfer_mem2mem;
       
   180     else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
       
   181         ch->transfer_fn = transfer_mem2fifo;
       
   182     else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
       
   183         ch->transfer_fn = transfer_fifo2mem;
       
   184     else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
       
   185         ch->transfer_fn = transfer_fifo2fifo;
       
   186     else
       
   187         ch->transfer_fn = ch->dma->transfer_fn;
       
   188 
       
   189     ch->update = (dst != soc_dma_port_other);
       
   190 }
       
   191 
       
   192 static void soc_dma_ch_freq_update(struct dma_s *s)
       
   193 {
       
   194     if (s->enabled_count)
       
   195         /* We completely ignore channel priorities and stuff */
       
   196         s->channel_freq = s->soc.freq / s->enabled_count;
       
   197     else
       
   198         /* TODO: Signal that we want to disable the functional clock and let
       
   199          * the platform code decide what to do with it, i.e. check that
       
   200          * auto-idle is enabled in the clock controller and if we are stopping
       
   201          * the clock, do the same with any parent clocks that had only one
       
   202          * user keeping them on and auto-idle enabled.  */;
       
   203 }
       
   204 
       
   205 void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
       
   206 {
       
   207     struct dma_s *dma = (struct dma_s *) ch->dma;
       
   208 
       
   209     dma->enabled_count += level - ch->enable;
       
   210 
       
   211     if (level)
       
   212         dma->ch_enable_mask |= 1 << ch->num;
       
   213     else
       
   214         dma->ch_enable_mask &= ~(1 << ch->num);
       
   215 
       
   216     if (level != ch->enable) {
       
   217         soc_dma_ch_freq_update(dma);
       
   218         ch->enable = level;
       
   219 
       
   220         if (!ch->enable)
       
   221             qemu_del_timer(ch->timer);
       
   222         else if (!ch->running)
       
   223             soc_dma_ch_run(ch);
       
   224         else
       
   225             soc_dma_ch_schedule(ch, 1);
       
   226     }
       
   227 }
       
   228 
       
   229 void soc_dma_reset(struct soc_dma_s *soc)
       
   230 {
       
   231     struct dma_s *s = (struct dma_s *) soc;
       
   232 
       
   233     s->soc.drqbmp = 0;
       
   234     s->ch_enable_mask = 0;
       
   235     s->enabled_count = 0;
       
   236     soc_dma_ch_freq_update(s);
       
   237 }
       
   238 
       
   239 /* TODO: take a functional-clock argument */
       
   240 struct soc_dma_s *soc_dma_init(int n)
       
   241 {
       
   242     int i;
       
   243     struct dma_s *s = qemu_mallocz(sizeof(*s) + n * sizeof(*s->ch));
       
   244 
       
   245     s->chnum = n;
       
   246     s->soc.ch = s->ch;
       
   247     for (i = 0; i < n; i ++) {
       
   248         s->ch[i].dma = &s->soc;
       
   249         s->ch[i].num = i;
       
   250         s->ch[i].timer = qemu_new_timer(vm_clock, soc_dma_ch_run, &s->ch[i]);
       
   251     }
       
   252 
       
   253     soc_dma_reset(&s->soc);
       
   254     fifo_size = 0;
       
   255 
       
   256     return &s->soc;
       
   257 }
       
   258 
       
   259 void soc_dma_port_add_fifo(struct soc_dma_s *soc, target_phys_addr_t virt_base,
       
   260                 soc_dma_io_t fn, void *opaque, int out)
       
   261 {
       
   262     struct memmap_entry_s *entry;
       
   263     struct dma_s *dma = (struct dma_s *) soc;
       
   264 
       
   265     dma->memmap = qemu_realloc(dma->memmap, sizeof(*entry) *
       
   266                     (dma->memmap_size + 1));
       
   267     entry = soc_dma_lookup(dma, virt_base);
       
   268 
       
   269     if (dma->memmap_size) {
       
   270         if (entry->type == soc_dma_port_mem) {
       
   271             if (entry->addr <= virt_base &&
       
   272                             entry->addr + entry->u.mem.size > virt_base) {
       
   273                 fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
       
   274                                 " collides with RAM region at " TARGET_FMT_lx
       
   275                                 "-" TARGET_FMT_lx "\n", __FUNCTION__,
       
   276                                 (target_ulong) virt_base,
       
   277                                 (target_ulong) entry->addr, (target_ulong)
       
   278                                 (entry->addr + entry->u.mem.size));
       
   279                 exit(-1);
       
   280             }
       
   281 
       
   282             if (entry->addr <= virt_base)
       
   283                 entry ++;
       
   284         } else
       
   285             while (entry < dma->memmap + dma->memmap_size &&
       
   286                             entry->addr <= virt_base) {
       
   287                 if (entry->addr == virt_base && entry->u.fifo.out == out) {
       
   288                     fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
       
   289                                     " collides FIFO at " TARGET_FMT_lx "\n",
       
   290                                     __FUNCTION__, (target_ulong) virt_base,
       
   291                                     (target_ulong) entry->addr);
       
   292                     exit(-1);
       
   293                 }
       
   294 
       
   295                 entry ++;
       
   296             }
       
   297 
       
   298         memmove(entry + 1, entry,
       
   299                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
       
   300                         (uint8_t *) entry);
       
   301     } else
       
   302         dma->memmap_size ++;
       
   303 
       
   304     entry->addr          = virt_base;
       
   305     entry->type          = soc_dma_port_fifo;
       
   306     entry->u.fifo.fn     = fn;
       
   307     entry->u.fifo.opaque = opaque;
       
   308     entry->u.fifo.out    = out;
       
   309 }
       
   310 
       
   311 void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
       
   312                 target_phys_addr_t virt_base, size_t size)
       
   313 {
       
   314     struct memmap_entry_s *entry;
       
   315     struct dma_s *dma = (struct dma_s *) soc;
       
   316 
       
   317     dma->memmap = qemu_realloc(dma->memmap, sizeof(*entry) *
       
   318                     (dma->memmap_size + 1));
       
   319     entry = soc_dma_lookup(dma, virt_base);
       
   320 
       
   321     if (dma->memmap_size) {
       
   322         if (entry->type == soc_dma_port_mem) {
       
   323             if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
       
   324                             (entry->addr <= virt_base &&
       
   325                              entry->addr + entry->u.mem.size > virt_base)) {
       
   326                 fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
       
   327                                 " collides with RAM region at " TARGET_FMT_lx
       
   328                                 "-" TARGET_FMT_lx "\n", __FUNCTION__,
       
   329                                 (target_ulong) virt_base,
       
   330                                 (target_ulong) (virt_base + size),
       
   331                                 (target_ulong) entry->addr, (target_ulong)
       
   332                                 (entry->addr + entry->u.mem.size));
       
   333                 exit(-1);
       
   334             }
       
   335 
       
   336             if (entry->addr <= virt_base)
       
   337                 entry ++;
       
   338         } else {
       
   339             if (entry->addr >= virt_base &&
       
   340                             entry->addr < virt_base + size) {
       
   341                 fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
       
   342                                 " collides with FIFO at " TARGET_FMT_lx
       
   343                                 "\n", __FUNCTION__,
       
   344                                 (target_ulong) virt_base,
       
   345                                 (target_ulong) (virt_base + size),
       
   346                                 (target_ulong) entry->addr);
       
   347                 exit(-1);
       
   348             }
       
   349 
       
   350             while (entry < dma->memmap + dma->memmap_size &&
       
   351                             entry->addr <= virt_base)
       
   352                 entry ++;
       
   353 	}
       
   354 
       
   355         memmove(entry + 1, entry,
       
   356                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
       
   357                         (uint8_t *) entry);
       
   358     } else
       
   359         dma->memmap_size ++;
       
   360 
       
   361     entry->addr          = virt_base;
       
   362     entry->type          = soc_dma_port_mem;
       
   363     entry->u.mem.base    = phys_base;
       
   364     entry->u.mem.size    = size;
       
   365 }
       
   366 
       
   367 /* TODO: port removal for ports like PCMCIA memory */