Path: blob/main/sys/contrib/vchiq/interface/vchiq_arm/vchiq_2835_arm.c
48383 views
/**1* Copyright (c) 2010-2012 Broadcom. All rights reserved.2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6* 1. Redistributions of source code must retain the above copyright7* notice, this list of conditions, and the following disclaimer,8* without modification.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12* 3. The names of the above-listed copyright holders may not be used13* to endorse or promote products derived from this software without14* specific prior written permission.15*16* ALTERNATIVELY, this software may be distributed under the terms of the17* GNU General Public License ("GPL") version 2, as published by the Free18* Software Foundation.19*20* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR24* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,25* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,26* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR27* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF28* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING29* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS30* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31*/3233#include <interface/compat/vchi_bsd.h>3435#include <sys/malloc.h>36#include <sys/rwlock.h>3738#include <vm/vm.h>39#include <vm/pmap.h>40#include <vm/vm_extern.h>41#include <vm/vm_kern.h>42#include <vm/vm_map.h>43#include <vm/vm_object.h>44#include <vm/vm_page.h>45#include <vm/vm_pager.h>46#include <vm/vm_param.h>4748#include <machine/bus.h>49#include <machine/cpu.h>50#include <arm/broadcom/bcm2835/bcm2835_mbox.h>51#include <arm/broadcom/bcm2835/bcm2835_vcbus.h>5253MALLOC_DEFINE(M_VCPAGELIST, "vcpagelist", "VideoCore pagelist memory");5455#define TOTAL_SLOTS (VCHIQ_SLOT_ZERO_SLOTS + 2 * 32)5657#define VCHIQ_DOORBELL_IRQ IRQ_ARM_DOORBELL_058#define VCHIQ_ARM_ADDRESS(x) ((void *)PHYS_TO_VCBUS(pmap_kextract((vm_offset_t)(x))))5960#include "vchiq_arm.h"61#include "vchiq_2835.h"62#include "vchiq_connected.h"63#include "vchiq_killable.h"6465#define MAX_FRAGMENTS (VCHIQ_NUM_CURRENT_BULKS * 2)6667int g_cache_line_size = 32;68static int g_fragment_size;6970typedef struct vchiq_2835_state_struct {71int inited;72VCHIQ_ARM_STATE_T arm_state;73} VCHIQ_2835_ARM_STATE_T;7475static char *g_slot_mem;76static int g_slot_mem_size;77vm_paddr_t g_slot_phys;78/* BSD DMA */79bus_dma_tag_t bcm_slots_dma_tag;80bus_dmamap_t bcm_slots_dma_map;8182static char *g_fragments_base;83static char *g_free_fragments;84struct semaphore g_free_fragments_sema;8586static DEFINE_SEMAPHORE(g_free_fragments_mutex);8788typedef struct bulkinfo_struct {89PAGELIST_T *pagelist;90bus_dma_tag_t pagelist_dma_tag;91bus_dmamap_t pagelist_dma_map;92void *buf;93size_t size;94} BULKINFO_T;9596static int97create_pagelist(char __user *buf, size_t count, unsigned short type,98struct proc *p, BULKINFO_T *bi);99100static void101free_pagelist(BULKINFO_T *bi, int actual);102103static void104vchiq_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)105{106bus_addr_t *addr;107108if (err)109return;110111addr = (bus_addr_t*)arg;112*addr = PHYS_TO_VCBUS(segs[0].ds_addr);113}114115static int116copyout_page(vm_page_t p, size_t offset, void *kaddr, size_t size)117{118uint8_t *dst;119120dst = (uint8_t*)pmap_quick_enter_page(p);121if (!dst)122return ENOMEM;123124memcpy(dst + offset, kaddr, size);125126pmap_quick_remove_page((vm_offset_t)dst);127128return 0;129}130131int __init132vchiq_platform_init(VCHIQ_STATE_T *state)133{134VCHIQ_SLOT_ZERO_T *vchiq_slot_zero;135int frag_mem_size;136int err;137int i;138139/* Allocate space for the channels in coherent memory */140g_slot_mem_size = PAGE_ALIGN(TOTAL_SLOTS * VCHIQ_SLOT_SIZE);141g_fragment_size = 2*g_cache_line_size;142frag_mem_size = PAGE_ALIGN(g_fragment_size * MAX_FRAGMENTS);143144err = bus_dma_tag_create(145NULL,146PAGE_SIZE, 0, /* alignment, boundary */147BUS_SPACE_MAXADDR_32BIT, /* lowaddr */148BUS_SPACE_MAXADDR, /* highaddr */149NULL, NULL, /* filter, filterarg */150g_slot_mem_size + frag_mem_size, 1, /* maxsize, nsegments */151g_slot_mem_size + frag_mem_size, 0, /* maxsegsize, flags */152NULL, NULL, /* lockfunc, lockarg */153&bcm_slots_dma_tag);154155err = bus_dmamem_alloc(bcm_slots_dma_tag, (void **)&g_slot_mem,156BUS_DMA_COHERENT | BUS_DMA_WAITOK, &bcm_slots_dma_map);157if (err) {158vchiq_log_error(vchiq_core_log_level, "Unable to allocate channel memory");159err = -ENOMEM;160goto failed_alloc;161}162163err = bus_dmamap_load(bcm_slots_dma_tag, bcm_slots_dma_map, g_slot_mem,164g_slot_mem_size + frag_mem_size, vchiq_dmamap_cb,165&g_slot_phys, 0);166167if (err) {168vchiq_log_error(vchiq_core_log_level, "cannot load DMA map");169err = -ENOMEM;170goto failed_load;171}172173WARN_ON(((int)g_slot_mem & (PAGE_SIZE - 1)) != 0);174175vchiq_slot_zero = vchiq_init_slots(g_slot_mem, g_slot_mem_size);176if (!vchiq_slot_zero) {177err = -EINVAL;178goto failed_init_slots;179}180181vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_OFFSET_IDX] =182(int)g_slot_phys + g_slot_mem_size;183vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_COUNT_IDX] =184MAX_FRAGMENTS;185186g_fragments_base = (char *)(g_slot_mem + g_slot_mem_size);187g_slot_mem_size += frag_mem_size;188189g_free_fragments = g_fragments_base;190for (i = 0; i < (MAX_FRAGMENTS - 1); i++) {191*(char **)&g_fragments_base[i*g_fragment_size] =192&g_fragments_base[(i + 1)*g_fragment_size];193}194*(char **)&g_fragments_base[i*g_fragment_size] = NULL;195_sema_init(&g_free_fragments_sema, MAX_FRAGMENTS);196197if (vchiq_init_state(state, vchiq_slot_zero, 0/*slave*/) !=198VCHIQ_SUCCESS) {199err = -EINVAL;200goto failed_vchiq_init;201}202203bcm_mbox_write(BCM2835_MBOX_CHAN_VCHIQ, (unsigned int)g_slot_phys);204205vchiq_log_info(vchiq_arm_log_level,206"vchiq_init - done (slots %x, phys %x)",207(unsigned int)vchiq_slot_zero, g_slot_phys);208209vchiq_call_connected_callbacks();210211return 0;212213failed_vchiq_init:214failed_init_slots:215bus_dmamap_unload(bcm_slots_dma_tag, bcm_slots_dma_map);216failed_load:217bus_dmamem_free(bcm_slots_dma_tag, g_slot_mem, bcm_slots_dma_map);218failed_alloc:219bus_dma_tag_destroy(bcm_slots_dma_tag);220221return err;222}223224void __exit225vchiq_platform_exit(VCHIQ_STATE_T *state)226{227228bus_dmamap_unload(bcm_slots_dma_tag, bcm_slots_dma_map);229bus_dmamem_free(bcm_slots_dma_tag, g_slot_mem, bcm_slots_dma_map);230bus_dma_tag_destroy(bcm_slots_dma_tag);231}232233VCHIQ_STATUS_T234vchiq_platform_init_state(VCHIQ_STATE_T *state)235{236VCHIQ_STATUS_T status = VCHIQ_SUCCESS;237state->platform_state = kzalloc(sizeof(VCHIQ_2835_ARM_STATE_T), GFP_KERNEL);238((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->inited = 1;239status = vchiq_arm_init_state(state, &((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->arm_state);240if(status != VCHIQ_SUCCESS)241{242((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->inited = 0;243}244return status;245}246247VCHIQ_ARM_STATE_T*248vchiq_platform_get_arm_state(VCHIQ_STATE_T *state)249{250if(!((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->inited)251{252BUG();253}254return &((VCHIQ_2835_ARM_STATE_T*)state->platform_state)->arm_state;255}256257int258vchiq_copy_from_user(void *dst, const void *src, int size)259{260261if (((vm_offset_t)(src)) < VM_MIN_KERNEL_ADDRESS) {262int error = copyin(src, dst, size);263return error ? VCHIQ_ERROR : VCHIQ_SUCCESS;264}265else266bcopy(src, dst, size);267268return 0;269}270271VCHIQ_STATUS_T272vchiq_prepare_bulk_data(VCHIQ_BULK_T *bulk, VCHI_MEM_HANDLE_T memhandle,273void *offset, int size, int dir)274{275BULKINFO_T *bi;276int ret;277278WARN_ON(memhandle != VCHI_MEM_HANDLE_INVALID);279bi = malloc(sizeof(*bi), M_VCPAGELIST, M_WAITOK | M_ZERO);280281ret = create_pagelist((char __user *)offset, size,282(dir == VCHIQ_BULK_RECEIVE)283? PAGELIST_READ284: PAGELIST_WRITE,285current,286bi);287if (ret != 0)288return VCHIQ_ERROR;289290bulk->handle = memhandle;291bulk->data = VCHIQ_ARM_ADDRESS(bi->pagelist);292293/* Store the pagelist address in remote_data, which isn't used by the294slave. */295bulk->remote_data = bi;296297return VCHIQ_SUCCESS;298}299300void301vchiq_complete_bulk(VCHIQ_BULK_T *bulk)302{303if (bulk && bulk->remote_data && bulk->actual)304free_pagelist((BULKINFO_T *)bulk->remote_data, bulk->actual);305}306307void308vchiq_transfer_bulk(VCHIQ_BULK_T *bulk)309{310/*311* This should only be called on the master (VideoCore) side, but312* provide an implementation to avoid the need for ifdefery.313*/314BUG();315}316317void318vchiq_dump_platform_state(void *dump_context)319{320char buf[80];321int len;322len = snprintf(buf, sizeof(buf),323" Platform: 2835 (VC master)");324vchiq_dump(dump_context, buf, len + 1);325}326327VCHIQ_STATUS_T328vchiq_platform_suspend(VCHIQ_STATE_T *state)329{330return VCHIQ_ERROR;331}332333VCHIQ_STATUS_T334vchiq_platform_resume(VCHIQ_STATE_T *state)335{336return VCHIQ_SUCCESS;337}338339void340vchiq_platform_paused(VCHIQ_STATE_T *state)341{342}343344void345vchiq_platform_resumed(VCHIQ_STATE_T *state)346{347}348349int350vchiq_platform_videocore_wanted(VCHIQ_STATE_T* state)351{352return 1; // autosuspend not supported - videocore always wanted353}354355int356vchiq_platform_use_suspend_timer(void)357{358return 0;359}360void361vchiq_dump_platform_use_state(VCHIQ_STATE_T *state)362{363vchiq_log_info(vchiq_arm_log_level, "Suspend timer not in use");364}365void366vchiq_platform_handle_timeout(VCHIQ_STATE_T *state)367{368(void)state;369}370/*371* Local functions372*/373374static void375pagelist_page_free(vm_page_t pp)376{377vm_page_unwire(pp, PQ_INACTIVE);378}379380/* There is a potential problem with partial cache lines (pages?)381** at the ends of the block when reading. If the CPU accessed anything in382** the same line (page?) then it may have pulled old data into the cache,383** obscuring the new data underneath. We can solve this by transferring the384** partial cache lines separately, and allowing the ARM to copy into the385** cached area.386387** N.B. This implementation plays slightly fast and loose with the Linux388** driver programming rules, e.g. its use of __virt_to_bus instead of389** dma_map_single, but it isn't a multi-platform driver and it benefits390** from increased speed as a result.391*/392393static int394create_pagelist(char __user *buf, size_t count, unsigned short type,395struct proc *p, BULKINFO_T *bi)396{397PAGELIST_T *pagelist;398vm_page_t* pages;399unsigned long *addrs;400unsigned int num_pages, i;401vm_offset_t offset;402int pagelist_size;403char *addr, *base_addr, *next_addr;404int run, addridx, actual_pages;405int err;406vm_paddr_t pagelist_phys;407vm_paddr_t pa;408409offset = (vm_offset_t)buf & (PAGE_SIZE - 1);410num_pages = (count + offset + PAGE_SIZE - 1) / PAGE_SIZE;411412bi->pagelist = NULL;413bi->buf = buf;414bi->size = count;415416/* Allocate enough storage to hold the page pointers and the page417** list418*/419pagelist_size = sizeof(PAGELIST_T) +420(num_pages * sizeof(unsigned long)) +421(num_pages * sizeof(pages[0]));422423err = bus_dma_tag_create(424NULL,425PAGE_SIZE, 0, /* alignment, boundary */426BUS_SPACE_MAXADDR_32BIT, /* lowaddr */427BUS_SPACE_MAXADDR, /* highaddr */428NULL, NULL, /* filter, filterarg */429pagelist_size, 1, /* maxsize, nsegments */430pagelist_size, 0, /* maxsegsize, flags */431NULL, NULL, /* lockfunc, lockarg */432&bi->pagelist_dma_tag);433434err = bus_dmamem_alloc(bi->pagelist_dma_tag, (void **)&pagelist,435BUS_DMA_COHERENT | BUS_DMA_WAITOK, &bi->pagelist_dma_map);436if (err) {437vchiq_log_error(vchiq_core_log_level, "Unable to allocate pagelist memory");438err = -ENOMEM;439goto failed_alloc;440}441442err = bus_dmamap_load(bi->pagelist_dma_tag, bi->pagelist_dma_map, pagelist,443pagelist_size, vchiq_dmamap_cb,444&pagelist_phys, 0);445446if (err) {447vchiq_log_error(vchiq_core_log_level, "cannot load DMA map for pagelist memory");448err = -ENOMEM;449goto failed_load;450}451452vchiq_log_trace(vchiq_arm_log_level,453"create_pagelist - %x (%d bytes @%p)", (unsigned int)pagelist, count, buf);454455if (!pagelist)456return -ENOMEM;457458addrs = pagelist->addrs;459pages = (vm_page_t*)(addrs + num_pages);460461actual_pages = vm_fault_quick_hold_pages(&p->p_vmspace->vm_map,462(vm_offset_t)buf, count,463(type == PAGELIST_READ ? VM_PROT_WRITE : 0 ) | VM_PROT_READ, pages, num_pages);464465if (actual_pages != num_pages) {466if (actual_pages > 0)467vm_page_unhold_pages(pages, actual_pages);468free(pagelist, M_VCPAGELIST);469return (-ENOMEM);470}471472pagelist->length = count;473pagelist->type = type;474pagelist->offset = offset;475476/* Group the pages into runs of contiguous pages */477478base_addr = (void *)PHYS_TO_VCBUS(VM_PAGE_TO_PHYS(pages[0]));479next_addr = base_addr + PAGE_SIZE;480addridx = 0;481run = 0;482483for (i = 1; i < num_pages; i++) {484addr = (void *)PHYS_TO_VCBUS(VM_PAGE_TO_PHYS(pages[i]));485if ((addr == next_addr) && (run < (PAGE_SIZE - 1))) {486next_addr += PAGE_SIZE;487run++;488} else {489addrs[addridx] = (unsigned long)base_addr + run;490addridx++;491base_addr = addr;492next_addr = addr + PAGE_SIZE;493run = 0;494}495}496497addrs[addridx] = (unsigned long)base_addr + run;498addridx++;499500/* Partial cache lines (fragments) require special measures */501if ((type == PAGELIST_READ) &&502((pagelist->offset & (g_cache_line_size - 1)) ||503((pagelist->offset + pagelist->length) &504(g_cache_line_size - 1)))) {505char *fragments;506507if (down_interruptible(&g_free_fragments_sema) != 0) {508free(pagelist, M_VCPAGELIST);509return -EINTR;510}511512WARN_ON(g_free_fragments == NULL);513514down(&g_free_fragments_mutex);515fragments = g_free_fragments;516WARN_ON(fragments == NULL);517g_free_fragments = *(char **) g_free_fragments;518up(&g_free_fragments_mutex);519pagelist->type =520PAGELIST_READ_WITH_FRAGMENTS +521(fragments - g_fragments_base)/g_fragment_size;522}523524pa = pmap_extract(PCPU_GET(curpmap), (vm_offset_t)buf);525dcache_wbinv_poc((vm_offset_t)buf, pa, count);526527bus_dmamap_sync(bi->pagelist_dma_tag, bi->pagelist_dma_map, BUS_DMASYNC_PREWRITE);528529bi->pagelist = pagelist;530531return 0;532533failed_load:534bus_dmamem_free(bi->pagelist_dma_tag, bi->pagelist, bi->pagelist_dma_map);535failed_alloc:536bus_dma_tag_destroy(bi->pagelist_dma_tag);537538return err;539}540541static void542free_pagelist(BULKINFO_T *bi, int actual)543{544vm_page_t*pages;545unsigned int num_pages, i;546PAGELIST_T *pagelist;547548pagelist = bi->pagelist;549550vchiq_log_trace(vchiq_arm_log_level,551"free_pagelist - %x, %d (%lu bytes @%p)", (unsigned int)pagelist, actual, pagelist->length, bi->buf);552553num_pages =554(pagelist->length + pagelist->offset + PAGE_SIZE - 1) /555PAGE_SIZE;556557pages = (vm_page_t*)(pagelist->addrs + num_pages);558559/* Deal with any partial cache lines (fragments) */560if (pagelist->type >= PAGELIST_READ_WITH_FRAGMENTS) {561char *fragments = g_fragments_base +562(pagelist->type - PAGELIST_READ_WITH_FRAGMENTS)*g_fragment_size;563int head_bytes, tail_bytes;564head_bytes = (g_cache_line_size - pagelist->offset) &565(g_cache_line_size - 1);566tail_bytes = (pagelist->offset + actual) &567(g_cache_line_size - 1);568569if ((actual >= 0) && (head_bytes != 0)) {570if (head_bytes > actual)571head_bytes = actual;572573copyout_page(pages[0],574pagelist->offset,575fragments,576head_bytes);577}578579if ((actual >= 0) && (head_bytes < actual) &&580(tail_bytes != 0)) {581582copyout_page(pages[num_pages-1],583(((vm_offset_t)bi->buf + actual) % PAGE_SIZE) - tail_bytes,584fragments + g_cache_line_size,585tail_bytes);586}587588down(&g_free_fragments_mutex);589*(char **) fragments = g_free_fragments;590g_free_fragments = fragments;591up(&g_free_fragments_mutex);592up(&g_free_fragments_sema);593}594595for (i = 0; i < num_pages; i++) {596if (pagelist->type != PAGELIST_WRITE) {597vm_page_dirty(pages[i]);598pagelist_page_free(pages[i]);599}600}601602bus_dmamap_unload(bi->pagelist_dma_tag, bi->pagelist_dma_map);603bus_dmamem_free(bi->pagelist_dma_tag, bi->pagelist, bi->pagelist_dma_map);604bus_dma_tag_destroy(bi->pagelist_dma_tag);605606free(bi, M_VCPAGELIST);607}608609610