Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Model of Bus Mastering ADC implemented in QEMU

27 views
Skip to first unread message

Wojciech M. Zabolotny

unread,
Apr 12, 2011, 4:49:50 PM4/12/11
to
Archive-name: qemu-wzab-tst1
Submitted-by: wz...@ise.pw.edu.pl (Wojciech M. Zabolotny)

The attached sources implement a simple model of Bus Mastering
Analog to Digital Converter for QEMU.
The sources consist of the following files:
1. drv_tst1.c - driver for the ADC written for kernel 2.6.38 (works
also with kernel used in Knoppix 6.4.4)
2. Makefile - the makefile used to compile the driver
3. user_tst1.c - sample program receiving data from the modelled device
you should compile it with:
$gcc -o user_tst1 user_tst1.c
4. wzab_tst1.patch - Patch file which should be applied to qemu 0.14.0
sources. You should unpack the qemu-0.14.0.tar.gz
file in some directory, and then execute:
patch -p 0 < wzab_tst1.patch
in this directory. Then you should add two next files
to the qemu-0.14.0/hw directory:
5. wzab_tst1.c - this is the main file implementing the model of ADC
You should copy it to the qemu-0.14.0/hw directory
6. wzab_tst1.h - this is the header file with registers offsets.
You should copy it to the qemu-0.14.0/hw directory,
but you should also copy it (or create the symlink to it)
to the directory in which you will compile drv_tst1.c
and user_tst1.c

Explanation how the modelled device works is given in the begining of the
wzab_tst1.c file.
All sources are free. They are licensed according to GPL v2 license or
according to QEMU license. If license terms are not given, please consider
this source as PUBLIC DOMAIN.

Wojciech M. Zabolotny
wzab<at>ise.pw.edu.pl

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.9).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `#!/bin/sh' line above, then type `sh FILE'.
#
lock_dir=_sh07951
# Made on 2011-04-12 22:33 CEST by <wzab@wzab>.
# Source directory was `/tmp/wzlap/qemu_ster/adc'.
#
# Existing files will *not* be overwritten, unless `-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 7667 -rw-r--r-- drv_tst1.c
# 177 -rw-r--r-- Makefile
# 2341 -rw-r--r-- user_tst1.c
# 8622 -rw-r--r-- wzab_tst1.c
# 254 -rw-r--r-- wzab_tst1.h
# 852 -rw-r--r-- wzab_tst1.patch
#
MD5SUM=${MD5SUM-md5sum}
f=`${MD5SUM} --version | egrep '^md5sum .*(core|text)utils'`
test -n "${f}" && md5check=true || md5check=false
${md5check} || \
echo 'Note: not verifying md5sums. Consider installing GNU coreutils.'
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=FAILED
locale_dir=FAILED
first_param="$1"
for dir in $PATH
do
if test "$gettext_dir" = FAILED && test -f $dir/gettext \
&& ($dir/gettext --version >/dev/null 2>&1)
then
case `$dir/gettext --version 2>&1 | sed 1q` in
*GNU*) gettext_dir=$dir ;;
esac
fi
if test "$locale_dir" = FAILED && test -f $dir/shar \
&& ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
then
locale_dir=`$dir/shar --print-text-domain-dir`
fi
done
IFS="$save_IFS"
if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED
then
echo=echo
else
TEXTDOMAINDIR=$locale_dir
export TEXTDOMAINDIR
TEXTDOMAIN=sharutils
export TEXTDOMAIN
echo="$gettext_dir/gettext -s"
fi
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null
then if (echo -n test; echo 1,2,3) | grep n >/dev/null
then shar_n= shar_c='
'
else shar_n=-n shar_c= ; fi
else shar_n= shar_c='\c' ; fi
f=shar-touch.$$
st1=200112312359.59
st2=123123592001.59
st2tr=123123592001.5 # old SysV 14-char limit
st3=1231235901

if touch -am -t ${st1} ${f} >/dev/null 2>&1 && \
test ! -f ${st1} && test -f ${f}; then
shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'

elif touch -am ${st2} ${f} >/dev/null 2>&1 && \
test ! -f ${st2} && test ! -f ${st2tr} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'

elif touch -am ${st3} ${f} >/dev/null 2>&1 && \
test ! -f ${st3} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$2 "$8"'

else
shar_touch=:
echo
${echo} 'WARNING: not restoring timestamps. Consider getting and
installing GNU `touch'\'', distributed in GNU coreutils...'
echo
fi
rm -f ${st1} ${st2} ${st2tr} ${st3} ${f}
#
if test ! -d ${lock_dir} ; then :
else ${echo} "lock directory ${lock_dir} exists"
exit 1
fi
if mkdir ${lock_dir}
then ${echo} "x - created lock directory ${lock_dir}."
else ${echo} "x - failed to create lock directory ${lock_dir}."
exit 1
fi
# ============= drv_tst1.c ==============
if test -f 'drv_tst1.c' && test "$first_param" != -c; then
${echo} "x - SKIPPING drv_tst1.c (file already exists)"
else
${echo} "x - extracting drv_tst1.c (text)"
sed 's/^X//' << 'SHAR_EOF' > 'drv_tst1.c' &&
/* wzab_tst1 device driver
X * Copyright (C) 2011 by Wojciech M. Zabolotny
X * wzab<at>ise.pw.edu.pl
X * Significantly based on multiple drivers included in
X * sources of Linux
X * Therefore tis saource is licensed on GPL v2
X */
X
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL v2");
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include "wzab_tst1.h"
X
#define SUCCESS 0
#define DEVICE_NAME "wzab_tst1"
X
int irq=5; //By default we use irq 5
module_param(irq,int,0);
MODULE_PARM_DESC(irq, "IRQ line number");
X
volatile uint32_t * fmem=NULL; //Pointer to registers area
volatile void * fdata=NULL; //Pointer to data buffer
int phys_addr = 0x41000000;
module_param(phys_addr,int,0);
MODULE_PARM_DESC(phys_addr,"Physical address of register area");
X
void cleanup_tst1( void );
void cleanup_tst1( void );
int init_tst1( void );
static int tst1_open(struct inode *inode, struct file *file);
static int tst1_release(struct inode *inode, struct file *file);
ssize_t tst1_read(struct file *filp,
X char __user *buf,size_t count, loff_t *off);
ssize_t tst1_write(struct file *filp,
X const char __user *buf,size_t count, loff_t *off);
loff_t tst1_llseek(struct file *filp, loff_t off, int origin);
X
int tst1_mmap(struct file *filp, struct vm_area_struct *vma);
X
dev_t my_dev=0;
struct cdev * my_cdev = NULL;
static struct class *class_my_tst = NULL;
X
/* Queue for reading process */
DECLARE_WAIT_QUEUE_HEAD (readqueue);
X
/* Interrupt service routine */
irqreturn_t tst1_irq(int irq, void * dev_id)
{
X // First we check if our device reqauests interrupt
X uint32_t status;
X status = *(fmem+TST1_READP);
X if(status & (1<<30)) {
X //Yes, this is our device
X //Block interrupts, they will be turned on by the reading process
X *(fmem+TST1_CTRL) = 0;
X //Wake up the reading process
X wake_up_interruptible(&readqueue);
X return IRQ_HANDLED;
X }
X return IRQ_NONE; //Our device does not request interrupt
};
X
X
struct file_operations Fops = {
X .owner = THIS_MODULE,
X .read=tst1_read, /* read */
X .write=tst1_write, /* write */
X .open=tst1_open,
X .release=tst1_release, /* a.k.a. close */
X .llseek=no_llseek,
X .mmap=tst1_mmap
};
X
int init_tst1( void )
{
X int res;
X class_my_tst = class_create(THIS_MODULE, "my_tst");
X if (IS_ERR(class_my_tst)) {
X printk(KERN_ERR "Error creating my_tst class.\n");
X res=PTR_ERR(class_my_tst);
X goto err1;
X }
X /* Alocate device number */
X res=alloc_chrdev_region(&my_dev, 0, 1, DEVICE_NAME);
X if(res) {
X printk ("<1>Alocation of the device number for %s failed\n",
X DEVICE_NAME);
X goto err1;
X };
X my_cdev = cdev_alloc( );
X if(my_cdev == NULL) {
X printk ("<1>Allocation of cdev for %s failed\n",
X DEVICE_NAME);
X goto err1;
X }
X my_cdev->ops = &Fops;
X my_cdev->owner = THIS_MODULE;
X /* Add character device */
X res=cdev_add(my_cdev, my_dev, 1);
X if(res) {
X printk ("<1>Registration of the device number for %s failed\n",
X DEVICE_NAME);
X goto err1;
X };
X /* Create pointer needed to access registers */
X fmem = ioremap(phys_addr, 0x1000); //One page should be enough
X if(!fmem) {
X printk ("<1>Mapping of memory for %s registers failed\n",
X DEVICE_NAME);
X res= -ENOMEM;
X goto err1;
X }
X /* Create pointer needed to access ADC data */
X fdata = (void *) __get_free_pages(GFP_KERNEL, 2);
X if(!fdata) {
X printk ("<1>Mapping of memory for %s data failed\n",
X DEVICE_NAME);
X res= -ENOMEM;
X goto err1;
X }
X /* Write physical addresses of buffer pages to registers */
X {
X int i;
X for(i=0;i<4;i++)
X *(fmem+TST1_PAGE1+i) = virt_to_phys(fdata+4096*i);
X }
X device_create(class_my_tst,NULL,my_dev,NULL,"my_tst%d",MINOR(my_dev));
X printk ("<1>%s The major device number is %d.\n",
X "Registeration is a success.",
X MAJOR(my_dev));
X return 0;
X err1:
X /* In case of error free resources */
X cleanup_tst1();
X return res;
}
X
module_init(init_tst1);
X
/* Cleanup resources */
void cleanup_tst1( void )
{
X if(my_dev && class_my_tst) {
X device_destroy(class_my_tst,my_dev);
X }
X if(fdata) free_pages((unsigned long)fdata,2);
X if(fmem) iounmap(fmem);
X if(my_cdev) cdev_del(my_cdev);
X my_cdev=NULL;
X unregister_chrdev_region(my_dev, 1);
X if(class_my_tst) {
X class_destroy(class_my_tst);
X class_my_tst=NULL;
X }
X
}
module_exit(cleanup_tst1);
X
X
static int tst1_open(struct inode *inode,
X struct file *file)
{
X int res=0;
X nonseekable_open(inode, file);
X res=request_irq(irq,tst1_irq,0,DEVICE_NAME,NULL);
X if(res) {
X printk (KERN_INFO "wzab_tst1: I can't connect irq %i\n", irq);
X irq = -1;
X }
X return SUCCESS;
}
X
static int tst1_release(struct inode *inode,
X struct file *file)
{
#ifdef DEBUG
X printk ("<1>device_release(%p,%p)\n", inode, file);
#endif
X *(fmem+TST1_DIV) = 0; //Switch sampling off
X if(irq>=0) free_irq(irq,NULL); //Free interrupt
X return SUCCESS;
}
X
ssize_t tst1_read(struct file *filp,
X char __user *buf,size_t count, loff_t *off)
{
X uint32_t val;
X if (count != 4) return -EINVAL; //Only 4-byte accesses allowed
X if ((*(fmem+TST1_CTRL)) & 0x1) {
X ssize_t res;
X //Interrupts are on, so we should sleep and wait for interrupt
X res=wait_event_interruptible(readqueue,(*(fmem+TST1_READP)) & (3<<30));
X if(res) return res; //Signal received!
X }
X //Read pointers
X val = *(fmem+TST1_READP) ; //fills bits 31 (overrun),30(irq) and bits 0-11 (readp)
X val |= (*(fmem+TST1_WRITEP)) << 12 ; //fills bits 23-12 (writep)
X if(__copy_to_user(buf,&val,4)) return -EFAULT;
X return 4;
}
X
ssize_t tst1_write(struct file *filp,
X const char __user *buf,size_t count, loff_t *off)
{
X uint32_t val;
X if (count != 4) return -EINVAL; //Only 4-byte access allowed
X __copy_from_user(&val,buf,4);
X if(val & (1<<31)) {
X //Requested staret of sampling
X //Write 0 to the divider (to switch off sampling if it was on)
X *(fmem+TST1_DIV) = 0;
X //Write value to the divider
X *(fmem+TST1_DIV) = val & ~(1<<31);
X } else {
X /* This write notifies about reception of data */
X *(fmem+TST1_READP) = val;
X *(fmem+TST1_CTRL) = 1; //switch on IRQs
X }
X return 4;
}
X
X
void tst1_vma_open (struct vm_area_struct * area)
{ }
X
void tst1_vma_close (struct vm_area_struct * area)
{ }
X
static struct vm_operations_struct tst1_vm_ops = {
X .open=tst1_vma_open,
X .close=tst1_vma_close,
};
X
int tst1_mmap(struct file *filp,
X struct vm_area_struct *vma)
{
X unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
X if(off==0) {
X //Mapping of registers
X unsigned long physical = phys_addr;
X unsigned long vsize = vma->vm_end - vma->vm_start;
X unsigned long psize = 1000; //One page is enough
X if(vsize>psize)
X return -EINVAL;
X remap_pfn_range(vma,vma->vm_start, physical >> PAGE_SHIFT , vsize, vma->vm_page_prot);
X if (vma->vm_ops)
X return -EINVAL; //It should never happen
X vma->vm_ops = &tst1_vm_ops;
X tst1_vma_open(vma); //This time no open(vma) was called
X return 0;
X } else {
X //Mapping of buffer
X //In the newest kernel we may use remap_pfn_range to map RAM.
X unsigned long physical = virt_to_phys(fdata);
X unsigned long vsize = vma->vm_end - vma->vm_start;
X unsigned long psize = 4*4096; //4 pages of buffer!
X if(vsize>psize)
X return -EINVAL;
X remap_pfn_range(vma,vma->vm_start, physical >> PAGE_SHIFT , vsize, vma->vm_page_prot);
X if (vma->vm_ops)
X return -EINVAL; //It should never happen
X vma->vm_ops = &tst1_vm_ops;
X tst1_vma_open(vma); //This time no open(vma) was called
X return 0;
X }
}
X
SHAR_EOF
(set 20 11 04 12 22 32 56 'drv_tst1.c'
eval "${shar_touch}") && \
chmod 0644 'drv_tst1.c'
if test $? -ne 0
then ${echo} "restore of drv_tst1.c failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'drv_tst1.c': 'MD5 check failed'
) << \SHAR_EOF
71be9e93237860dc9ea730dee8b4e367 drv_tst1.c
SHAR_EOF
else
test `LC_ALL=C wc -c < 'drv_tst1.c'` -ne 7667 && \
${echo} "restoration warning: size of 'drv_tst1.c' is not 7667"
fi
fi
# ============= Makefile ==============
if test -f 'Makefile' && test "$first_param" != -c; then
${echo} "x - SKIPPING Makefile (file already exists)"
else
${echo} "x - extracting Makefile (text)"
sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
ifneq ($(KERNELRELEASE),)
X obj-m := drv_tst1.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
X $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
endif
SHAR_EOF
(set 20 11 04 12 22 11 57 'Makefile'
eval "${shar_touch}") && \
chmod 0644 'Makefile'
if test $? -ne 0
then ${echo} "restore of Makefile failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'Makefile': 'MD5 check failed'
) << \SHAR_EOF
b97d817bf5e5ab14128af9f253366792 Makefile
SHAR_EOF
else
test `LC_ALL=C wc -c < 'Makefile'` -ne 177 && \
${echo} "restoration warning: size of 'Makefile' is not 177"
fi
fi
# ============= user_tst1.c ==============
if test -f 'user_tst1.c' && test "$first_param" != -c; then
${echo} "x - SKIPPING user_tst1.c (file already exists)"
else
${echo} "x - extracting user_tst1.c (text)"
sed 's/^X//' << 'SHAR_EOF' > 'user_tst1.c' &&
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#include <unistd.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include "wzab_tst1.h"
int main()
{
X unsigned long val;
X FILE * dout;
X printf("I'm trying to open our device!\n");
X fflush(stdout);
X int plik=open("/dev/my_tst0", O_RDWR);
X if(plik==-1)
X {
X printf("I can't open device!\n");
X fflush(stdout);
X exit(1);
X }
X printf("Device opened!\n");
X fflush(stdout);
X //Open file for writing of received data
X dout=fopen("data.bin","w");
X if(dout==NULL) {
X printf("I can't open output file!\n");
X fflush(stdout);
X exit(1);
X }
X //Map registers (in fact it should not be necessary!
X volatile unsigned long * devm1 = (unsigned long *)
X mmap(0,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED,
X plik,0x0000000);
X if(devm1 == (void *) -1l)
X {
X perror("I can't map registers\n");
X }
X //Map data buffer
X volatile unsigned long * devm2 = (unsigned long *)
X mmap(0,0x4000,PROT_READ | PROT_WRITE,MAP_SHARED,
X plik,0x0001000);
X if(devm2 == (void *) -1l)
X {
X perror("I can't map data buffer!\n");
X }
X printf("Memory mapped: regs: %x buffer: %x\n",devm1, devm2);
X fflush(stdout);
X //Start sampling 100 samples every 0.5 second
X val=500000000 | (1<<31);
X if(write(plik,&val,4) != 4) {
X printf("error writing!\n");
X exit(1);
X }
X //Enter the data reception loop! Only CTRL+C can exit it!
X while(1) {
X unsigned long r,readp,writep;
X if(read(plik,&r,4) != 4) {
X printf("error reading!\n");
X exit(1);
X }
X //Check for overrun
X if(r & (1<<31)) {
X printf("overrun occured!\n");
X exit(1);
X }
X //Unpack pointers from read value
X writep=(r>>12) & ((1<<12)-1);
X readp= r & ((1<<12)-1);
X printf("readp=%x , writep=%x\n",readp,writep);
X //Read the data and copy them to the file
X //Use buffered write to speed write access!
X while(readp != writep) {
X if(fwrite(devm2+readp,4,1,dout)!=1) {
X printf("error writing data!\n");
X exit(1);
X }
X readp=(readp+1) & ((1<<12)-1);
X }
X //Confirm reception of data
X r=writep;
X if(write(plik,&r,4)!=4){
X printf("error writing to device!\n");
X exit(1);
X }
X //Report reception of data
X printf("data from %d to %d \n",readp,writep);
X }
}
SHAR_EOF
(set 20 11 04 12 22 32 57 'user_tst1.c'
eval "${shar_touch}") && \
chmod 0644 'user_tst1.c'
if test $? -ne 0
then ${echo} "restore of user_tst1.c failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'user_tst1.c': 'MD5 check failed'
) << \SHAR_EOF
25f4d4513bf36040d81c9ebf22a37c67 user_tst1.c
SHAR_EOF
else
test `LC_ALL=C wc -c < 'user_tst1.c'` -ne 2341 && \
${echo} "restoration warning: size of 'user_tst1.c' is not 2341"
fi
fi
# ============= wzab_tst1.c ==============
if test -f 'wzab_tst1.c' && test "$first_param" != -c; then
${echo} "x - SKIPPING wzab_tst1.c (file already exists)"
else
${echo} "x - extracting wzab_tst1.c (text)"
sed 's/^X//' << 'SHAR_EOF' > 'wzab_tst1.c' &&
/*
X * The code below implements a simple Analog to Digital converter
X * for QEMU, which uses Bus Mastering DMA to transfer data to the PC.
X * The real device may be implemented in an FPGA connected to the
X * system device, however this model is assumed to be used mainly
X * as a didicatical aid for students learning how to write and debug
X * Linux device drivers.
X * The code was written by Wojciech M. Zabolotny (wzab<at>ise.pw.edu.pl)
X * in March and April 2011, however it is significantly based on different
X * source codes provided
X * in the QEMU sources.
X * Therefore I leave the original license:
X *
X * Copyright (c) 2003 Fabrice Bellard
X *
X * Permission is hereby granted, free of charge, to any person obtaining a copy
X * of this software and associated documentation files (the "Software"), to deal
X * in the Software without restriction, including without limitation the rights
X * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
X * copies of the Software, and to permit persons to whom the Software is
X * furnished to do so, subject to the following conditions:
X *
X * The above copyright notice and this permission notice shall be included in
X * all copies or substantial portions of the Software.
X *
X * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
X * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
X * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
X * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
X * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
X * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
X * THE SOFTWARE.
X *
X *
X * Description of the ADC
X *
X * Device uses 8 32-bit registers (with symbolic names defined in the file wzab_tst1.h)
X * TST1_DIV - writing to this register sets the internal frequency divider, defining
X * the sampling rate of the converter. Writing of zero stops sampling at all.
X * Writing of nonzero value - starts sampling
X * TST1_PAGE1 - TST_PAGE4
X * The 4 registers above should be written with physical addresses of the
X * pages allocated as the buffer for acquired data.
X * Therefore the device offers 16*4096=16384 bytes buffer (i.e. buffer for
X * 4096 32-bit samples).
X * TST1_READP - The read pointer for data buffer (counts in 32-bit dwords!)
X * the 30-th bit informs if the device requests interrupt
X * the 31-st bit informs if "overrun" occured (see below).
X * TST1_WRITEP - The write pointer for data buffer (count in 32-bit dwords!).
X * If the new acquired value is about to overwrite the previous one, which
X * has not been received yet, the "overrun" status is set, and new data are ignored.
X * To restart correct operation, you have to write TST1_DIV with 0, and then
X * with the correct value.
X * TST1_CTRL - Currently only one (0th) bit of this register is used. If this bit is set
X * to 1, the device generates interrupt after new samples arrive. If this
X * bit is cleared, the device does not generate the interrupt
X *
X * Currently device is statically connected to the physical addresses starting from 0x41000000
X * and uses ISA IRQ 5.
X */
#include "hw.h"
#include "console.h"
#include "pc.h"
#include "pci.h"
#include "qemu-timer.h"
//#define DEBUG_wzab1 1
X
#include "wzab_tst1.h"
X
/* Variables storing state of the device. What is the meaning
X of this "state"? */
typedef struct {
X uint32_t regs[TST1_REGS_NUM];
X uint32_t writep, readp;
X uint32_t overrun;
X uint32_t irq_pending;
X uint32_t div;
X uint32_t sample_val;
X QEMUTimer * timer;
X qemu_irq irq;
} wzab1CommonState ;
X
X
/* called for read accesses to our register memory area */
uint32_t wzab1_mem_readl(void *opaque, target_phys_addr_t addr)
{
#ifdef DEBUG_wzab1
X printf("Memory read: address %x ", addr);
#endif
X wzab1CommonState *s = opaque;
X uint32_t ret;
X addr = (addr/4) & 0x0ff;
X //Special cases
X if(addr==TST1_READP) {
X ret = s->readp | (s->overrun ? (1<<31) : 0) | (s->irq_pending ? (1<<30) : 0);
#ifdef DEBUG_wzab1
X printf(" value %x\n",ret);
#endif
X return ret;
X }
X if(addr==TST1_WRITEP) {
X ret = s->writep;
#ifdef DEBUG_wzab1
X printf(" value %x\n",ret);
#endif
X return s->writep;
X }
X //normal case
X if(addr<TST1_REGS_NUM) {
X ret = s->regs[addr];
#ifdef DEBUG_wzab1
X printf(" value %x\n",ret);
#endif
X return ret;
X }
}
X
X
/* called for write accesses to our register memory area */
void wzab1_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
{
X wzab1CommonState *s = opaque;
#ifdef DEBUG_wzab1
X printf("wzab1: zapis pod adres = 0x%08x, 0x%08x\n", addr, val);
#endif
X /* convert to wzab1 memory offset */
X addr = (addr/4) & 0xff;
X /* always write value to the register */
X if(addr<TST1_REGS_NUM) {
X s->regs[addr]=val;
X }
X switch(addr) {
X case TST1_DIV:
X if(val) {
X //Start sampling
X s->readp = 0;
X s->writep = 0;
X s->overrun = 0;
X qemu_mod_timer(s->timer,qemu_get_clock(vm_clock)+s->regs[TST1_DIV]);
X } else {
X //Stop sampling
#ifdef DEBUG_wzab1
X printf("deleting timer!\n");
#endif
X qemu_del_timer(s->timer);
X s->readp = 0;
X s->writep = 0;
X s->overrun = 0;
X }
X break;
X case TST1_PAGE1:
X case TST1_PAGE2:
X case TST1_PAGE3:
X case TST1_PAGE4:
X break;
X case TST1_READP:
X s->readp = val & ((1<<12)-1);
X break;
X case TST1_CTRL:
X if((val & 1) == 0) {
X //Switch the IRQ off
X qemu_irq_lower(s->irq);
X } else {
X //Rise the IRQ if it's pending
X if(s->irq_pending) qemu_irq_raise(s->irq);
X }
X break;
X default:
X return;
X break;
X }
}
X
/* The procedure below emulates 100 of sampling cycles */
static void wzab1_tick(void *opaque)
{
X wzab1CommonState * s = opaque;
X //rearm timer
X qemu_mod_timer(s->timer,qemu_get_clock(vm_clock)+s->regs[TST1_DIV]);
X //Write 100 samples to the buffer
X {
X int i;
X for(i=0;i<100;i++) {
X //We have place for 4096 samples, so we use only 12-bit pointer
X int new_writep = (s->writep+1) & ((1<<12) - 1);
X if (new_writep == s->readp) { //Overrun!
X s->overrun = 1;
X qemu_del_timer(s->timer); //Stop sampling!
X break;
X } else if (s->overrun == 0) {
X
X int pagenr = (s->writep >> 10); // bits 11 and 10 - page nr
X int dwordnr = s->writep & ((1<<10) - 1); // bits 9 to 0 - dword nr
#ifdef DEBUG_wzab1
X printf("trying to write in %08x \n",s->regs[TST1_PAGE1+pagenr]+dwordnr*4);
#endif
X cpu_physical_memory_write(s->regs[TST1_PAGE1+pagenr]+dwordnr*4,(uint8_t *)&(s->sample_val),4);
X s->writep = new_writep;
X //Modify the sample value. Currently we generate the sawtooth waveform with values between 0 and 1110
X s->sample_val = s->sample_val+1;
X if(s->sample_val == 1111) s->sample_val = 0;
X }
X }
X }
X //Raise IRQ
X s->irq_pending = 1;
#ifdef DEBUG_wzab1
X printf("Request IRQ!\n");
#endif
X qemu_irq_raise(s->irq);
}
X
void wzab1_common_reset( wzab1CommonState * s)
{
X memset(s->regs,0,sizeof(s->regs));
#ifdef DEBUG_wzab1
X printf("wzab_tst1 reset!\n");
#endif
}
X
static void wzab1_reset(void * opaque)
{
X wzab1CommonState * s = opaque;
X wzab1_common_reset(s);
}
X
CPUReadMemoryFunc * const wzab1_mem_read[3] = {
X NULL,
X NULL,
X wzab1_mem_readl,
};
X
/* We handle only 32-bit accesses! */
CPUWriteMemoryFunc * const wzab1_mem_write[3] = {
X NULL,
X NULL,
X wzab1_mem_writel,
};
X
static const VMStateDescription vmstate_wzab = {
X .name = "wzab1",
X .version_id = 1,
X .minimum_version_id = 1,
X .minimum_version_id_old = 1,
X .fields = (VMStateField []) {
X //VMSTATE_BUFFER(regs,wzab1CommonState),
X VMSTATE_TIMER(timer,wzab1CommonState),
X VMSTATE_END_OF_LIST()
X }
};
X
wzab1CommonState *s;
X
/* Initialization of our model */
void wzab1_init()
{
X int wzab1_io_memory;
X s = qemu_mallocz(sizeof(wzab1CommonState));
X qemu_register_reset(wzab1_reset, s);
X s->timer = qemu_new_timer(vm_clock, wzab1_tick, s);
X wzab1_io_memory = cpu_register_io_memory(wzab1_mem_read, wzab1_mem_write, s, DEVICE_LITTLE_ENDIAN);
X // Our device is connected at physical address 0x41000000
X cpu_register_physical_memory(isa_mem_base + 0x41000000, TST1_REGS_NUM*4,
X wzab1_io_memory);
X qemu_register_coalesced_mmio(isa_mem_base + 0x41000000, TST1_REGS_NUM*4);
X // Our device uses IRQ 5
X s->irq = isa_reserve_irq(5);
X qemu_irq_lower(s->irq);
X vmstate_register(NULL,0,&vmstate_wzab,s);
#ifdef DEBUG_wzab1
X printf("wzab1: initalized!\n");
#endif
}
X
SHAR_EOF
(set 20 11 04 12 22 32 57 'wzab_tst1.c'
eval "${shar_touch}") && \
chmod 0644 'wzab_tst1.c'
if test $? -ne 0
then ${echo} "restore of wzab_tst1.c failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'wzab_tst1.c': 'MD5 check failed'
) << \SHAR_EOF
4b5d4da02ba5be1e33cc52a9ebf92ac9 wzab_tst1.c
SHAR_EOF
else
test `LC_ALL=C wc -c < 'wzab_tst1.c'` -ne 8622 && \
${echo} "restoration warning: size of 'wzab_tst1.c' is not 8622"
fi
fi
# ============= wzab_tst1.h ==============
if test -f 'wzab_tst1.h' && test "$first_param" != -c; then
${echo} "x - SKIPPING wzab_tst1.h (file already exists)"
else
${echo} "x - extracting wzab_tst1.h (text)"
sed 's/^X//' << 'SHAR_EOF' > 'wzab_tst1.h' &&
//Definitions of 32-bit registers
#define TST1_DIV 0
#define TST1_READP 1
#define TST1_WRITEP 2
#define TST1_CTRL 3
#define TST1_PAGE1 4
#define TST1_PAGE2 5
#define TST1_PAGE3 6
#define TST1_PAGE4 7
//Number of registers
#define TST1_REGS_NUM 8
SHAR_EOF
(set 20 11 04 03 13 33 01 'wzab_tst1.h'
eval "${shar_touch}") && \
chmod 0644 'wzab_tst1.h'
if test $? -ne 0
then ${echo} "restore of wzab_tst1.h failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'wzab_tst1.h': 'MD5 check failed'
) << \SHAR_EOF
44ab1f2fad71870d517f610e488512dc wzab_tst1.h
SHAR_EOF
else
test `LC_ALL=C wc -c < 'wzab_tst1.h'` -ne 254 && \
${echo} "restoration warning: size of 'wzab_tst1.h' is not 254"
fi
fi
# ============= wzab_tst1.patch ==============
if test -f 'wzab_tst1.patch' && test "$first_param" != -c; then
${echo} "x - SKIPPING wzab_tst1.patch (file already exists)"
else
${echo} "x - extracting wzab_tst1.patch (text)"
sed 's/^X//' << 'SHAR_EOF' > 'wzab_tst1.patch' &&
diff -r -U 3 /tmp/b/qemu-0.14.0/hw/pc_piix.c /tmp/a/qemu-0.14.0/hw/pc_piix.c
--- /tmp/b/qemu-0.14.0/hw/pc_piix.c 2011-02-16 15:44:04.000000000 +0100
+++ /tmp/a/qemu-0.14.0/hw/pc_piix.c 2011-04-11 23:30:38.000000000 +0200
@@ -184,6 +184,7 @@
X if (pci_enabled) {
X pc_pci_device_init(pci_bus);
X }
+ wzab1_init();
X }
X
X static void pc_init_pci(ram_addr_t ram_size,
diff -r -U 3 /tmp/b/qemu-0.14.0/Makefile.target /tmp/a/qemu-0.14.0/Makefile.target
--- /tmp/b/qemu-0.14.0/Makefile.target 2011-02-16 15:44:04.000000000 +0100
+++ /tmp/a/qemu-0.14.0/Makefile.target 2011-03-22 22:10:03.000000000 +0100
@@ -213,6 +213,7 @@
X
X # Hardware support
X obj-i386-y += vga.o
+obj-i386-y += wzab_tst1.o
X obj-i386-y += mc146818rtc.o i8259.o pc.o
X obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o
X obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o
SHAR_EOF
(set 20 11 04 12 22 27 37 'wzab_tst1.patch'
eval "${shar_touch}") && \
chmod 0644 'wzab_tst1.patch'
if test $? -ne 0
then ${echo} "restore of wzab_tst1.patch failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'wzab_tst1.patch': 'MD5 check failed'
) << \SHAR_EOF
7973370354f3ce891decb84bd237e971 wzab_tst1.patch
SHAR_EOF
else
test `LC_ALL=C wc -c < 'wzab_tst1.patch'` -ne 852 && \
${echo} "restoration warning: size of 'wzab_tst1.patch' is not 852"
fi
fi
if rm -fr ${lock_dir}
then ${echo} "x - removed lock directory ${lock_dir}."
else ${echo} "x - failed to remove lock directory ${lock_dir}."
exit 1
fi
exit 0

wzab

unread,
Jun 11, 2011, 4:25:33 PM6/11/11
to
I've found a good source of information regarding writing of device
models for QEMU:
http://www.linux-kvm.org/wiki/images/f/fe/2010-forum-armbru-qdev.pdf
I hope to improve the model of ADC and implement other models with
this knowledge ;-).

wzab

unread,
Jun 14, 2011, 4:57:05 PM6/14/11
to
In the original sources call to
qemu_register_coalesced_mmio(isa_mem_base + 0x41000000,
TST1_REGS_NUM*4);
should be removed!

according to cpu-common.h:
105 /* Coalesced MMIO regions are areas where write operations can be
reordered.
106 * This usually implies that write operations are side-effect
free. This allows
107 * batching which can make a major impact on performance when
using
108 * virtualization.
109 */
110 void qemu_register_coalesced_mmio(target_phys_addr_t addr,
ram_addr_t size);

Of course writing to the memory in my model has serious side-effects
and
should NOT be reordered!

0 new messages