[Cbe-oss-dev] [PATCH] axonram: 1st version
Maxim Shchetynin
maxim at de.ibm.com
Wed Feb 14 10:06:13 EST 2007
Hello,
This patch implements access to Axon's DDR2 memory via character and block
devices.
Subject: cell: driver for DDR2 memory on AXON
From: Maxim Shchetynin <maxim.shchetynin at de.ibm.com>
Signed-off-by: Arnd Bergmann <arnd.bergmann at de.ibm.com>
Index: linux-2.6.20-axonram/arch/powerpc/Kconfig
===================================================================
diff -Nuar linux-2.6.20/arch/powerpc/Kconfig
linux-2.6.20-axonram/arch/powerpc/Kconfig
--- linux-2.6.20/arch/powerpc/Kconfig 2007-02-13 20:42:42.000000000
+0100
+++ linux-2.6.20-axonram/arch/powerpc/Kconfig 2007-02-13
20:54:06.000000000 +0100
@@ -727,6 +727,13 @@
If in doubt, say N here.
+config AXON_RAM
+ tristate "Axon DDR2 memory device driver"
+ depends on PPC_IBM_CELL_BLADE
+ default y
+ help
+ Block and character device driver for Axon DDR2 RAM support.
+
endmenu
source arch/powerpc/platforms/embedded6xx/Kconfig
Index: linux-2.6.20-axonram/arch/powerpc/sysdev/Makefile
===================================================================
diff -Nuar linux-2.6.20/arch/powerpc/sysdev/Makefile
linux-2.6.20-axonram/arch/powerpc/sysdev/Makefile
--- linux-2.6.20/arch/powerpc/sysdev/Makefile 2007-02-13
20:42:40.000000000 +0100
+++ linux-2.6.20-axonram/arch/powerpc/sysdev/Makefile 2007-02-13
20:54:40.000000000 +0100
@@ -13,6 +13,7 @@
obj-$(CONFIG_FSL_SOC) += fsl_soc.o
obj-$(CONFIG_TSI108_BRIDGE) += tsi108_pci.o tsi108_dev.o
obj-$(CONFIG_QUICC_ENGINE) += qe_lib/
+obj-$(CONFIG_AXON_RAM) += axonram.o
ifeq ($(CONFIG_PPC_MERGE),y)
obj-$(CONFIG_PPC_I8259) += i8259.o
Index: linux-2.6.20-axonram/arch/powerpc/sysdev/axonram.c
===================================================================
diff -Nuar linux-2.6.20/arch/powerpc/sysdev/axonram.c
linux-2.6.20-axonram/arch/powerpc/sysdev/axonram.c
--- linux-2.6.20/arch/powerpc/sysdev/axonram.c 1970-01-01
01:00:00.000000000 +0100
+++ linux-2.6.20-axonram/arch/powerpc/sysdev/axonram.c 2007-02-13
20:55:05.000000000 +0100
@@ -0,0 +1,501 @@
+/*
+ * (C) Copyright IBM Corp. 2006
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Axon DDR2 driver
+ */
+
+#include <asm/mmu.h>
+#include <asm/of_device.h>
+#include <asm/of_platform.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/prom.h>
+#include <asm/string.h>
+#include <asm/uaccess.h>
+#include <linux/bio.h>
+#include <linux/blkdev.h>
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/genhd.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/mm_types.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/types.h>
+
+#define AXON_RAM_MODULE_NAME "axonram"
+#define AXON_RAM_DEVICE_NAME "axonram"
+#define AXON_RAM_CHRDEV_NAME "axonchr"
+#define AXON_RAM_BLKDEV_NAME "axonram"
+#define AXON_RAM_MINORS_PER_DISK 16
+#define AXON_RAM_BLOCK_SHIFT 12 /* block size 4KB */
+#define AXON_RAM_SECTOR_SHIFT 9 /* sector size 512 */
+
+#define axon_ram_error(format, args...) \
+ printk(KERN_ERR AXON_RAM_MODULE_NAME ": " format, ##args);
+
+static int
+axon_ram_open(struct inode *, struct file *);
+static int
+axon_ram_release(struct inode *, struct file *);
+static loff_t
+axon_ram_llseek(struct file *, loff_t, int);
+static ssize_t
+axon_ram_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t
+axon_ram_write(struct file *, const char __user *, size_t, loff_t *);
+static int
+axon_ram_mmap(struct file *, struct vm_area_struct *);
+static int
+axon_ram_direct_access(struct block_device *, sector_t, unsigned long *);
+static int
+axon_ram_make_request(struct request_queue *, struct bio *);
+static int
+axon_ram_probe(struct of_device *, const struct of_device_id *);
+static int
+axon_ram_remove(struct of_device *);
+
+struct axon_ram_bank {
+ char chrdev_name[DEVICE_NAME_SIZE];
+ char blkdev_name[DEVICE_NAME_SIZE];
+ rwlock_t lock;
+ phys_addr_t phys_addr;
+ void __iomem *addr;
+ size_t size;
+ struct gendisk *disk;
+ struct miscdevice misc;
+ struct file_operations fops;
+};
+
+/**
+ * axon_ram_open - open() method for device
+ * @inode, @file: see file_operations method
+ */
+static int
+axon_ram_open(struct inode *inode, struct file *file)
+{
+ struct axon_ram_bank *bank;
+
+ /* Prevent access to memory bank using simultaneously both */
+ /* block and character device methods. */
+ if (S_ISCHR(inode->i_mode)) {
+ file->private_data = (void*) file->f_op +
+ sizeof(struct file_operations) -
+ sizeof(struct axon_ram_bank);
+ bank = (struct axon_ram_bank*) file->private_data;
+
+ /* Allow only a single open of character device. */
+ /* No access to device if it is already read-locked */
+ /* (opened at least one time as block device). */
+ if (!write_trylock(&bank->lock))
+ return -EBUSY;
+ } else if (S_ISBLK(inode->i_mode)) {
+ bank = (struct axon_ram_bank*)
+ inode->i_bdev->bd_disk->private_data;
+
+ /* Block device maybe opened multiple times therefore */
+ /* read-lock. No access to device if it is already */
+ /* write-locked (opened as character device). */
+ if (!read_trylock(&bank->lock))
+ return -EBUSY;
+ inode->i_bdev->bd_block_size = 1 << AXON_RAM_BLOCK_SHIFT;
+ } else {
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * axon_ram_release - release() method for device
+ * @inode, @file: see file_operations method
+ */
+static int
+axon_ram_release(struct inode *inode, struct file *file)
+{
+ struct axon_ram_bank *bank;
+
+ if (S_ISCHR(inode->i_mode)) {
+ bank = (struct axon_ram_bank*)
+ file->private_data;
+ write_unlock(&bank->lock);
+ } else if (S_ISBLK(inode->i_mode)) {
+ bank = (struct axon_ram_bank*)
+ inode->i_bdev->bd_disk->private_data;
+ read_unlock(&bank->lock);
+ } else {
+ /* Paranoja */
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * axon_ram_llseek - llseek() method for character device
+ * @file, @offset, @origin: see file_operations method
+ */
+static loff_t
+axon_ram_llseek(struct file *file, loff_t offset, int origin)
+{
+ struct axon_ram_bank *bank;
+
+ bank = (struct axon_ram_bank*) file->private_data;
+
+ switch (origin) {
+ case SEEK_CUR:
+ offset += file->f_pos;
+ break;
+
+ case SEEK_END:
+ offset += bank->size;
+ break;
+ }
+
+ /* No access outside a memory DIMM. */
+ if (offset < 0 || offset > bank->size)
+ return -EINVAL;
+
+ return file->f_pos = offset;
+}
+
+/**
+ * axon_ram_read - read() method for character device
+ * @file, @buffer, @size, @offset: see file_operations method
+ */
+static ssize_t
+axon_ram_read(struct file *file,
+ char __user *buffer, size_t size, loff_t *offset)
+{
+ struct axon_ram_bank *bank;
+
+ bank = (struct axon_ram_bank*) file->private_data;
+ /* No access outside a memory DIMM but still do as much as possible.
*/
+ if (*offset + size > bank->size)
+ size = bank->size - *offset;
+ copy_to_user(__user buffer, __iomem bank->addr + *offset, size);
+ *offset += size;
+
+ return size;
+}
+
+/**
+ * axon_ram_write - write() method for character device
+ * @file, @buffer, @size, @offset: see file_operations method
+ */
+static ssize_t
+axon_ram_write(struct file *file,
+ const char __user *buffer, size_t size, loff_t *offset)
+{
+ struct axon_ram_bank *bank;
+
+ bank = (struct axon_ram_bank*) file->private_data;
+ /* No access outside a memory DIMM but still do as much as possible.
*/
+ if (*offset + size > bank->size)
+ size = bank->size - *offset;
+ copy_from_user(__iomem bank->addr + *offset, __user buffer, size);
+ *offset += size;
+
+ return size;
+}
+
+/**
+ * axon_ram_mmap - mmap() method for character device
+ * @file, @vm: see file_operations method
+ */
+static int
+axon_ram_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct axon_ram_bank *bank;
+ unsigned long page;
+ unsigned long size;
+ pgprot_t page_prot;
+ int retval = 0;
+
+ bank = (struct axon_ram_bank*) file->private_data;
+
+ size = vma->vm_end - vma->vm_start;
+
+ /* No access outside a memory DIMM. */
+ if ((vma->vm_pgoff << PAGE_SHIFT) + size > bank->size) {
+ retval = -ERANGE;
+ goto out;
+ }
+
+ /* Transfer real address to page number. */
+ page = bank->phys_addr;
+ page >>= PAGE_SHIFT;
+ page += vma->vm_pgoff;
+
+ /* Switch off page-guard. */
+ page_prot = pgprot_val(vma->vm_page_prot);
+ page_prot &= ~(_PAGE_NO_CACHE | _PAGE_GUARDED);
+ vma->vm_page_prot = __pgprot(page_prot);
+ if (remap_pfn_range(vma, vma->vm_start, page, size,
vma->vm_page_prot))
+ retval = -EAGAIN;
+
+out:
+ return retval;
+}
+
+/**
+ * axon_ram_direct_access - direct_access() method for block device
+ * @device, @sector, @data: see block_device_operations method
+ */
+static int
+axon_ram_direct_access(struct block_device *device, sector_t sector,
+ unsigned long *data)
+{
+ struct axon_ram_bank *bank;
+ loff_t offset;
+
+ bank = (struct axon_ram_bank*) device->bd_disk->private_data;
+ /* Paranoja in it's best traditions. */
+ if (bank == NULL)
+ return -ENODEV;
+
+ /* No access outside a memory DIMM. */
+ offset = sector << AXON_RAM_SECTOR_SHIFT;
+ if (offset > bank->size)
+ return -ERANGE;
+
+ *data = (unsigned long) (bank->phys_addr + offset);
+
+ return 0;
+}
+
+/**
+ * axon_ram_make_request - make_request() method for block device
+ * @queue, @bio: see blk_queue_make_request()
+ */
+static int
+axon_ram_make_request(struct request_queue *queue, struct bio *bio)
+{
+ struct axon_ram_bank *bank;
+ unsigned long phys_mem, phys_end, user_mem;
+ unsigned long transfered;
+ struct bio_vec *vec;
+ int i;
+
+ bank = (struct axon_ram_bank*) bio->bi_bdev->bd_disk->private_data;
+ phys_mem = (unsigned long)
+ bank->addr + (bio->bi_sector << AXON_RAM_SECTOR_SHIFT);
+ phys_end = (unsigned long)
+ bank->addr + bank->size;
+ transfered = 0;
+ bio_for_each_segment(vec, bio, i) {
+ /* No access outside a memory DIMM. */
+ if (phys_mem + vec->bv_len > phys_end) {
+ bio_io_error(bio, bio->bi_size);
+ return -ERANGE;
+ }
+
+ user_mem = (unsigned long)
+ page_address(vec->bv_page) + vec->bv_offset;
+ if (bio_data_dir(bio) == READ)
+ memcpy((void*) user_mem, (void*) phys_mem, vec->bv_len);
+ else
+ memcpy((void*) phys_mem, (void*) user_mem, vec->bv_len);
+
+ phys_mem += vec->bv_len;
+ transfered += vec->bv_len;
+ }
+ bio_endio(bio, transfered, 0);
+
+ return 0;
+}
+
+static struct block_device_operations axon_ram_devops = {
+ .owner = THIS_MODULE,
+ .open = axon_ram_open,
+ .release = axon_ram_release,
+ .direct_access = axon_ram_direct_access
+};
+
+/**
+ * axon_ram_probe - probe() method for platform driver
+ * @device, @device_id: see of_platform_driver method
+ */
+static int
+axon_ram_probe(struct of_device *device, const struct of_device_id
*device_id)
+{
+ static int axon_ram_bank_id = -1;
+ struct axon_ram_bank *bank;
+ const u32 *reg0;
+ const u64 *reg1;
+ phys_addr_t phys_addr;
+ size_t size;
+
+ /* There should be a better solution... */
+ axon_ram_bank_id++;
+
+ printk("Found DDR2 memory DIMM %s%d on %s\n",
+ AXON_RAM_DEVICE_NAME, axon_ram_bank_id,
+ device->node->full_name);
+
+ reg0 = get_property(device->node, "reg", NULL);
+ if (reg0 == NULL) {
+ axon_ram_error("Cannot access device tree!\n");
+ return -EFAULT;
+ }
+ reg1 = (void*) reg0 + sizeof(u64);
+
+ phys_addr = of_translate_address(device->node, reg0);
+ if (phys_addr == OF_BAD_ADDR) {
+ axon_ram_error("Cannot access register in device tree!\n");
+ return -EFAULT;
+ }
+
+ size = *reg1;
+ if (size == 0) {
+ axon_ram_error("Cannot access register in device tree!\n");
+ return -EFAULT;
+ }
+
+ bank = kzalloc(sizeof(struct axon_ram_bank), GFP_KERNEL);
+ if (bank == NULL) {
+ axon_ram_error("Out of memory!\n");
+ return -ENOMEM;
+ }
+ rwlock_init(&bank->lock);
+ device->dev.platform_data = (void*) bank;
+
+ snprintf(bank->chrdev_name, DEVICE_NAME_SIZE, "%s%d",
+ AXON_RAM_CHRDEV_NAME, axon_ram_bank_id);
+ snprintf(bank->blkdev_name, DEVICE_NAME_SIZE, "%s%d",
+ AXON_RAM_BLKDEV_NAME, axon_ram_bank_id);
+ bank->phys_addr = phys_addr;
+ bank->addr = __iomem ioremap_flags(phys_addr, size, 0);
+ bank->size = size;
+
+ bank->misc.minor = MISC_DYNAMIC_MINOR;
+ bank->misc.name = bank->chrdev_name;
+ bank->misc.fops = &bank->fops;
+ bank->misc.parent = &device->dev;
+
+ bank->fops.owner = THIS_MODULE;
+ bank->fops.open = axon_ram_open;
+ bank->fops.release = axon_ram_release;
+ bank->fops.llseek = axon_ram_llseek;
+ bank->fops.read = axon_ram_read;
+ bank->fops.write = axon_ram_write;
+ bank->fops.mmap = axon_ram_mmap;
+
+ /* Don't quit if it fails - maybe at least block device would work.
*/
+ if (misc_register(&bank->misc) != 0)
+ axon_ram_error("Cannot register character device!\n");
+
+ bank->disk = alloc_disk(AXON_RAM_MINORS_PER_DISK);
+ if (bank->disk == NULL) {
+ axon_ram_error("Cannot register disk!\n");
+ if (bank->misc.minor != MISC_DYNAMIC_MINOR)
+ misc_deregister(&bank->misc);
+ kfree(bank);
+ return -EFAULT;
+ }
+
+ strcpy(bank->disk->disk_name, bank->blkdev_name);
+
+ bank->disk->major = register_blkdev(0, bank->disk->disk_name);
+ if (bank->disk->major < 0) {
+ axon_ram_error("Cannot register block device!\n");
+ if (bank->misc.minor != MISC_DYNAMIC_MINOR)
+ misc_deregister(&bank->misc);
+ del_gendisk(bank->disk);
+ kfree(bank);
+ return -EFAULT;
+ }
+
+ bank->disk->first_minor = 0;
+ bank->disk->fops = &axon_ram_devops;
+ bank->disk->queue = blk_alloc_queue(GFP_KERNEL);
+ bank->disk->private_data = (void*) bank;
+ bank->disk->driverfs_dev = &device->dev;
+
+ set_capacity(bank->disk, bank->size >> AXON_RAM_SECTOR_SHIFT);
+ blk_queue_make_request(bank->disk->queue, axon_ram_make_request);
+ blk_queue_hardsect_size(bank->disk->queue, 1 <<
AXON_RAM_BLOCK_SHIFT);
+ add_disk(bank->disk);
+
+ return 0;
+}
+
+/**
+ * axon_ram_remove - remove() method for platform driver
+ * @device: see of_platform_driver method
+ */
+static int
+axon_ram_remove(struct of_device *device)
+{
+ struct axon_ram_bank *bank;
+ int rc = 0;
+
+ bank = (struct axon_ram_bank*) device->dev.platform_data;
+
+ if (bank->misc.minor != MISC_DYNAMIC_MINOR)
+ rc |= misc_deregister(&bank->misc);
+
+ if (bank->disk != NULL) {
+ if (bank->disk->major > 0)
+ rc |= unregister_blkdev(bank->disk->major,
+ bank->disk->disk_name);
+ del_gendisk(bank->disk);
+ }
+
+ if (rc == 0)
+ kfree(bank);
+
+ return rc == 0 ? 0 : -EFAULT;
+}
+
+static struct of_device_id axon_ram_device_id[] = {
+ {
+ .type = "dma-memory"
+ },
+ {}
+};
+
+static struct of_platform_driver axon_ram_driver = {
+ .owner = THIS_MODULE,
+ .name = AXON_RAM_MODULE_NAME,
+ .match_table = axon_ram_device_id,
+ .probe = axon_ram_probe,
+ .remove = axon_ram_remove
+};
+
+/**
+ * axon_ram_init
+ */
+static int __init
+axon_ram_init(void)
+{
+ return of_register_platform_driver(&axon_ram_driver);
+}
+
+/**
+ * axon_ram_exit
+ */
+static void __exit
+axon_ram_exit(void)
+{
+ of_unregister_platform_driver(&axon_ram_driver);
+}
+
+module_init(axon_ram_init);
+module_exit(axon_ram_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("IBM Deutschland Entwicklung GmbH");
+MODULE_DESCRIPTION("Axon DDR2 RAM device driver for IBM Cell BE");
Mit freundlichen Grüßen / met vriendelijke groeten / avec regards
Maxim V. Shchetynin
IBM Deutschland Entwicklung GmbH
Linux für Cell BE
Schönaicher Straße 220
71032 Böblingen, Deutschland
Fahr nur so schnell wie dein Schutzengel fliegen kann!
More information about the cbe-oss-dev
mailing list