Amiga: Writes to Zorro III cards

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

here's a glimpse of the ZZ9000 driver I've been starting to put
together. Feedback and initial review would be highly appreciated ;)

- For some reason some of the writes to Zorro III ioremapped memory
don't seem to end up on the device. This leads to the USB device
sometimes showing up just fine, but at other times, the driver won't
find a partition table (as the buffer reads back all zeros) I've tried
a lot of different things, including compiling the code with -O0,
marking the functions noinline, and adding various delays and
__sync_synchronize() calls between writes. But on some boots the
driver loads and talks to the hardware just fine. When it fails/works,
it fails/works consistently across several rmmod/insmod cycles.
- In the network driver, I can see incoming and outgoing packets in
tcpdump on 68k. And if I arp -s the MAC addresses on both sides, I can
actually ping between hosts. But for some reason that I still have to
figure out, the ARP packages needed for discovery don't seem to end up
on the wire or in the right part of the network stack (I see arp
requests coming in on the Amiga in tcpdump, but they are never
answered)

Stefan

/* ZZ9000 memory map
 *
 * 
 *
 */

#define ZZ9000_VERSION "0.1 alpha"

#define HW_ADDRFIELDSIZE 6
#define HW_ETH_HDR_SIZE          14       /* ethernet header: dst, src, type */
#define HW_ETH_MTU               1500

#if 0
struct HWFrame {
   USHORT   hwf_Size;
   USHORT   hwf_Serial
   /* use layout of ethernet header here */
   UBYTE    hwf_DstAddr[HW_ADDRFIELDSIZE];
   UBYTE    hwf_SrcAddr[HW_ADDRFIELDSIZE];
   USHORT   hwf_Type;
   /*UBYTE    hwf_Data[MTU];*/
};
#endif

/* FIXME move to zorro_ids.h */
#define ZORRO_MANUF_MNT	0x6d6e
#define   ZORRO_PROD_MNT_ZZ9000_ZII  ZORRO_ID(MNT, 0x03, 0)
#define   ZORRO_PROD_MNT_ZZ9000_ZIII ZORRO_ID(MNT, 0x04, 0)

typedef volatile struct MNTZZ9KRegs {
  u16 hw_version;		// 00
  u16 mode;			// 02
  u16 config;			// 04 misc config bits
  u16 sprite_x;			// 06
  u16 sprite_y;			// 08

  u16 pan_ptr_hi;		// 0a
  u16 pan_ptr_lo;		// 0c
  u16 videocap_vmode;		// 0e

  u16 blitter_x1;		// 10 RECTOP
  u16 blitter_y1;		// 12
  u16 blitter_x2;		// 14
  u16 blitter_y2;		// 16
  u16 blitter_row_pitch;	// 18 destination pitch
  u16 blitter_x3;		// 1a
  u16 blitter_y3;		// 1c
  u16 blitter_rgb_hi;		// 1e
  u16 blitter_rgb_lo;		// 20
  u16 blitter_op_fillrect;	// 22
  u16 blitter_op_copyrect;	// 24
  u16 blitter_op_filltemplate;	// 26

  u16 blitter_src_hi;		// 28
  u16 blitter_src_lo;		// 2a
  u16 blitter_dst_hi;		// 2c
  u16 blitter_dst_lo;		// 2e

  u16 blitter_colormode;	// 30 destination colormode
  u16 blitter_src_pitch;	// 32
  u16 blitter_rgb2_hi;		// 34 background/secondary color
  u16 blitter_rgb2_lo;		// 36
  u16 blitter_op_p2c;		// 38
  u16 blitter_op_draw_line;	// 3a
  u16 blitter_op_p2d;		// 3c
  u16 blitter_op_invertrect;	// 3e

  // Reusing other register-accessible variables was getting a bit cluttered, and somewhat
  // of a coding hazard. Four additional user values should help for the time being.
  u16 blitter_user1; // 40
  u16 blitter_user2; // 42
  u16 blitter_user3; // 44
  u16 blitter_user4; // 46

  u16 sprite_bitmap; // 48
  u16 sprite_colors; // 4a
  u16 vblank_status; // 4c

  u16 un_3[0x19]; // 4e..7e

  u16 eth_tx;			// 80
  u16 eth_rx;			// 82

  u16 eth_hw_mac_hi;		// 84
  u16 eth_hw_mac_hi2;		// 86
  u16 eth_hw_mac_lo;		// 88
  u16 un_4[3]; // 8a,8c,8e

  u16 arm_run_hi;		// 90
  u16 arm_run_lo;		// 92
  u16 arm_argc;			// 94
  u16 arm_arg[8]; // 96,98,9a,9c..a4

  u16 un_5[5]; // a6..ae

  u16 arm_event_serial;		// b0
  u16 arm_event_code;		// b2

  u16 un_6[6]; // b4..be

  u16 fw_version;		// c0
  u16 un_7[7]; // c2..ce

  u16 usb_tx_hi;	// d0
  u16 usb_tx_lo;	// d2
  u16 usb_rx_hi;	// d4
  u16 usb_rx_lo;	// d6
  u16 usb_status;	// d8
  u16 usb_bufsel;	// da
  u32 usb_capacity;	// dc
  u16 un_8[6];		// f0..fa
  u16 debug;		// fc
  u16 un_9[1];		// fe

  // 0x002000-0x007fff RX buffer
  // 0x008000-0x009fff TX buffer
  // 0x00a000-0x00afff USB storage
  // 0x010000 framebuffer memory start

} MNTZZ9KRegs;

#define ZZ9K_RX  0x2000
#define ZZ9K_TX  0x8000
#define ZZ9K_USB 0xa000

struct zz9000_platform_data {
	void __iomem *registers;
	void __iomem *eth_rx_mem;
	void __iomem *eth_tx_mem;
	void __iomem *usb_mem;
};

int zz9000_usb_init(struct zz9000_platform_data *data);
int zz9000_usb_remove(struct zz9000_platform_data *data);

int zz9000_network_init(struct zz9000_platform_data *data);
int zz9000_network_remove(struct zz9000_platform_data *data);

/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#include <linux/console.h>
#include <linux/module.h>
#include <linux/zorro.h>
#include <asm/amigahw.h>
#include "zz9000.h"

static int zz9000_probe(struct zorro_dev *zdev,
			const struct zorro_device_id *ent)
{
	uint32_t board = zdev->resource.start;
	struct resource *res1, *res2, *res3, *res4;
	struct zz9000_platform_data *zz9000_data;
	MNTZZ9KRegs *regs;
	int ret = 0;

	res1 = request_mem_region(board, 0x100, "ZZ9000 registers");
	if (!res1) {
		dev_err(&zdev->dev, "Could not reserve register region\n");
		ret = -ENXIO;
		goto cleanup_regions;
	}

	res2 = request_mem_region(board + 0x2000, 0x6000, "ZZ9000 RX");
	if (!res2) {
		dev_err(&zdev->dev, "Could not reserve RX region\n");
		ret = -ENXIO;
		goto cleanup_regions;
	}

	res3 = request_mem_region(board + 0x8000, 0x2000, "ZZ9000 RX");
	if (!res3) {
		dev_err(&zdev->dev, "Could not reserve RX region\n");
		ret = -ENXIO;
		goto cleanup_regions;
	}

	res4 = request_mem_region(board + 0xa000, 0x1000, "ZZ9000 USB");
	if (!res4) {
		dev_err(&zdev->dev, "Could not reserve RX region\n");
		ret = -ENXIO;
		goto cleanup_regions;
	}

	zz9000_data =
	    devm_kzalloc(&zdev->dev, sizeof(*zz9000_data), GFP_KERNEL);

	if (!zz9000_data) {
		ret = -ENOMEM;
		goto cleanup_regions;
	}

	zorro_set_drvdata(zdev, zz9000_data);

	memset(zz9000_data, 0, sizeof(*zz9000_data));
	zz9000_data->registers = z_ioremap(board, 0x100);
	zz9000_data->eth_rx_mem = z_ioremap(board + 0x2000, 0x6000);
	zz9000_data->eth_tx_mem = z_ioremap(board + 0x8000, 0x2000);
	zz9000_data->usb_mem = z_ioremap(board + 0xa000, 0x1000);
	// TODO framebuffer :)
	if (!zz9000_data->registers || !zz9000_data->eth_rx_mem ||
			!zz9000_data->eth_tx_mem || !zz9000_data->usb_mem) {
		dev_err(&zdev->dev, "Could not ioremap device memory.\n");
		goto cleanup;
	}

	regs = (MNTZZ9KRegs *) zz9000_data->registers;
	printk(KERN_INFO "MNT ZZ9000 driver v%s (%s %s)\n", ZZ9000_VERSION, __DATE__, __TIME__);
	printk(KERN_INFO "MNT ZZ9000 Zorro %s found at 0x%08x\n",
	       zdev->id == ZORRO_PROD_MNT_ZZ9000_ZIII ? "III" : "II", board);
	printk(KERN_INFO "  HW version %d\n", regs->hw_version);
	printk(KERN_INFO "  FW version %d.%d\n", regs->fw_version >> 8,
	       regs->fw_version & 0xff);

	zz9000_usb_init(zz9000_data);

	zz9000_network_init(zz9000_data);

	if (!ret)
		return 0;

cleanup:
	if (zz9000_data->registers)
		iounmap(zz9000_data->registers);
	if (zz9000_data->eth_rx_mem)
		iounmap(zz9000_data->eth_rx_mem);
	if (zz9000_data->eth_tx_mem)
		iounmap(zz9000_data->eth_tx_mem);
	if (zz9000_data->usb_mem)
		iounmap(zz9000_data->usb_mem);

cleanup_regions:
	if (!res1)
		release_mem_region(board, 0x100);
	if (!res2)
		release_mem_region(board + 0x2000, 0x6000);
	if (!res3)
		release_mem_region(board + 0x8000, 0x2000);
	if (!res4)
		release_mem_region(board + 0xa000, 0x1000);

	return ret;
}

static void zz9000_remove(struct zorro_dev *zdev)
{
	struct zz9000_platform_data *zz9000_data = zorro_get_drvdata(zdev);

	zz9000_network_remove(zz9000_data);
	zz9000_usb_remove(zz9000_data);

	iounmap(zz9000_data->registers);
	iounmap(zz9000_data->eth_rx_mem);
	iounmap(zz9000_data->eth_tx_mem);
	iounmap(zz9000_data->usb_mem);

	release_mem_region(zdev->resource.start, 0x100);
	release_mem_region(zdev->resource.start + 0x2000, 0x6000);
	release_mem_region(zdev->resource.start + 0x8000, 0x2000);
	release_mem_region(zdev->resource.start + 0xa000, 0x1000);
}

static const struct zorro_device_id zz9000_zorro_tbl[] = {
	{ZORRO_PROD_MNT_ZZ9000_ZII},
	{ZORRO_PROD_MNT_ZZ9000_ZIII},
	{0}
};

MODULE_DEVICE_TABLE(zorro, zz9000_zorro_tbl);

static struct zorro_driver zz9000_driver = {
	.name = "zz9000",
	.id_table = zz9000_zorro_tbl,
	.probe = zz9000_probe,
	.remove = zz9000_remove,
};

static int __init zz9000_init(void)
{
	return zorro_register_driver(&zz9000_driver);
}

module_init(zz9000_init);

static void __exit zz9000_exit(void)
{
	zorro_unregister_driver(&zz9000_driver);
}

module_exit(zz9000_exit);

MODULE_DESCRIPTION("MNT ZZ9000 driver");
MODULE_AUTHOR("Stefan Reinauer <stefan.reinauer@xxxxxxxxxxxx>");
MODULE_LICENSE("GPL v2");
// 

#include <linux/console.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/zorro.h>
#include <asm/amigahw.h>
#include <asm/amigaints.h>
#include "zz9000.h"

static struct zz9000_platform_data *zpd;

/* Length definitions */
#define MAC_ADDR_LEN 6
static unsigned char zz9000_mac_addr[MAC_ADDR_LEN];

/* net_device referencing */
static struct net_device *device;
static struct net_device_stats *stats;

/* priv structure that holds the informations about the device. */
struct eth_priv {
	spinlock_t lock;
	struct net_device *dev;
	//struct napi_struct napi;
};

noinline static void zz9000_hw_tx(char *data, int len, struct net_device *dev)
{
	void *zz9000_tx_mem = zpd->eth_tx_mem;
	uint16_t ret;

	MNTZZ9KRegs *regs = zpd->registers;

	netdev_dbg(dev, "send data=%p len=%d\n", data, len);

	// print_hex_dump(KERN_DEBUG, "eth: ", DUMP_PREFIX_OFFSET,
	//	       16, 1, data, len, true);

	memcpy((void *)zz9000_tx_mem, data, len);

	__sync_synchronize();
	z_writew(len, &regs->eth_tx);
	__sync_synchronize();

	ret = z_readw(&regs->eth_tx);
	if (ret != 0)
		netdev_dbg(dev, "ret = %d\n", ret);
}

static uint32_t old_serial = 0;

/*
 * Read a packet out of the adapter and pass it to the upper layers
 */
static inline int recv_packet(struct net_device *dev)
{
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *) zpd->registers;
	unsigned short pktlen;
	struct sk_buff *skb;

	uint8_t *frame = (uint8_t *) zpd->eth_rx_mem;
	uint32_t ser = ((uint32_t) frame[2] << 8) | ((uint32_t) frame[3]);
	uint16_t tp = ((uint16_t) frame[16] << 8) | ((uint16_t) frame[17]);
	pktlen = ((uint32_t) frame[0] << 8) | ((uint32_t) frame[1]);

	/* There is not interrupt status register for network,
	 * so we use the serial number of the frame to determine
	 * whether there's a new packet
	 */
	if (old_serial == ser)
		return 0;
	old_serial = ser;

	netdev_dbg(dev, "[z] ser=%d len=%d tp=%d", ser, pktlen, tp);

	/* read packet length (excluding 32 bit crc) */
	//pktlen = sz;

	netdev_dbg(dev, "%s: %u\n", __func__, pktlen);

	if (!pktlen) {
		netdev_dbg(dev, "%s: pktlen == 0\n", __func__);
		dev->stats.rx_errors++;
		return -1;
	}

	skb = dev_alloc_skb(pktlen + 2);
	if (!skb) {
		netdev_dbg(dev, "%s: out of mem (buf_alloc failed)\n",
			   __func__);
		dev->stats.rx_dropped++;
		return -1;
	}

	skb->dev = dev;
	skb_reserve(skb, 2);	/* 16 Byte align  */
	skb_put(skb, pktlen);	/* make room */

#if 0
	print_hex_dump(KERN_DEBUG, "eth: ", DUMP_PREFIX_OFFSET,
		       16, 1, frame, pktlen, true);
#endif
	// Copy buffer to skb
	memcpy(skb->data, frame + 4, pktlen);

	// Mark this frame as accepted
	__sync_synchronize();
	z_writew(0x0001, &regs->eth_rx);
	__sync_synchronize();

	skb->protocol = eth_type_trans(skb, dev);
	netif_rx(skb);
	dev->stats.rx_packets++;
	dev->stats.rx_bytes += pktlen;

	/* and enqueue packet */
	return 1;
}

static irqreturn_t zz9000_eth_interrupt(int irq, void *dev_id)
{
	struct eth_priv *priv;
	int rcv = 1;

	/*
	 * As usual, check the "device" pointer to be sure it is
	 * really interrupting.
	 * Then assign "struct device *dev".
	 */
	struct net_device *dev = (struct net_device *)dev_id;
	/* ... and check with hw if it's really ours */

	/* paranoid */
	if (!dev)
		return IRQ_HANDLED;

	/* Lock the device */
	priv = netdev_priv(dev);
	spin_lock(&priv->lock);

	// Fetch all available packets.
	while (rcv) {
		rcv = recv_packet(dev);
	}

	/* Unlock the device and we are done */
	spin_unlock(&priv->lock);

	return IRQ_HANDLED;
}

static int zz9000_eth_open(struct net_device *dev)
{
	int ret;
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *) zpd->registers;

	/* Install interrupt handler for IRQ6.
	 * IRQ2 is currently not supported.
	 */
	ret = request_irq(IRQ_AMIGA_EXTER, zz9000_eth_interrupt, IRQF_SHARED,
			  "ZZ9000 Ethernet", dev);
	if (ret)
		return ret;

	/* Enable Interrupts on ZZ9000 */
	z_writew(z_readw(&regs->config) | 0x01, &regs->config);

	/* start up the transmission queue */
	netif_start_queue(dev);
	return 0;
}

static int zz9000_eth_close(struct net_device *dev)
{
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *) zpd->registers;

	/* Disable interrupts on ZZ9000 */
	z_writew(z_readw(&regs->config) & 0xfffe, &regs->config);

	/* shutdown the transmission queue */
	netif_stop_queue(dev);

	/* Remove interrupt handler */
	free_irq(IRQ_AMIGA_EXTER, dev);
	return 0;
}

/*
 * Transmit a packet (low level interface)
 */
static int zz9000_eth_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	int len;
	char *data, shortpkt[ETH_ZLEN];

	// printk(KERN_INFO "zz9000_eth_start_xmit(%s)\n", dev->name);
	data = skb->data;
	len = skb->len;
	if (len < ETH_ZLEN) {
		memset(shortpkt, 0, ETH_ZLEN);
		memcpy(shortpkt, skb->data, skb->len);
		len = ETH_ZLEN;
		data = shortpkt;
	}

	/* Actually send the packet out! */
	zz9000_hw_tx(data, len, dev);

	dev->stats.tx_packets++;
	dev->stats.tx_bytes += len;

	dev_kfree_skb(skb);

	return 0;		/* Our simple device can not fail */
}

/*
 * zz9000_eth_do_ioctl allows the driver to have Input/Output commands.
 * Missing implementation
 */
static int zz9000_eth_do_ioctl(struct net_device *dev, struct ifreq *ifr,
			       int cmd)
{
	printk(KERN_INFO "zz9000_eth_do_ioctl(%s): 0x%x\n", dev->name, cmd);
	return -1;
}

static struct net_device_stats *zz9000_eth_get_stats(struct net_device *dev)
{
	// printk(KERN_INFO "zz9000_eth_get_stats(%s)\n", dev->name);
	return stats;
}

/*
 * This is where ifconfig comes down and tells us who we are, etc.
 * We can just ignore this.
 */
static int zz9000_eth_config(struct net_device *dev, struct ifmap *map)
{
	printk("zz9000_eth_config(%s)\n", dev->name);
	if (dev->flags & IFF_UP) {
		return -EBUSY;
	}
	return 0;
}

/*
 * This will allow us to change the device mtu size.
 */
static int zz9000_eth_change_mtu(struct net_device *dev, int new_mtu)
{
	unsigned long flags = 0;
	struct eth_priv *priv = netdev_priv(dev);
	spinlock_t *lock = &priv->lock;

	printk(KERN_INFO "zz9000_eth_change_mtu(%s)\n", dev->name);

	/* Check ranges */
	if ((new_mtu < 68) || (new_mtu > 10000))	// Remember to see at the hardware documentation the right especification
		return -EINVAL;

	/*
	 * Do anything you need, and accept the value
	 */
	spin_unlock_irqrestore(lock, flags);
	printk(KERN_INFO "Old mtu: (%d) New mtu: (%d)", dev->mtu, new_mtu);
	dev->mtu = new_mtu;
	spin_unlock_irqrestore(lock, flags);
	return 0;		/* Sucess */
}

static void zz9000_eth_tx_timeout(struct net_device *dev)
{
	dev->stats.tx_errors++;

	netif_wake_queue(dev);
	return;
}

/*
 * Structure that holds all the options supported by the driver.
 */
static struct net_device_ops zz9000_netdev_ops = {
	.ndo_open = zz9000_eth_open,
	.ndo_stop = zz9000_eth_close,
	.ndo_start_xmit = zz9000_eth_start_xmit,
	.ndo_do_ioctl = zz9000_eth_do_ioctl,
	.ndo_get_stats = zz9000_eth_get_stats,
	.ndo_set_config = zz9000_eth_config,
	.ndo_change_mtu = zz9000_eth_change_mtu,
	.ndo_tx_timeout = zz9000_eth_tx_timeout,
	// Does NAPI make sense for this device?
	//.ndo_poll_controller = zz9000_eth_napi_poll;
};

static void zz9000_eth_setup(struct net_device *dev)
{
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *) zpd->registers;

	zz9000_mac_addr[0] = z_readw(&regs->eth_hw_mac_hi) >> 8;
	zz9000_mac_addr[1] = z_readw(&regs->eth_hw_mac_hi) & 0xff;
	zz9000_mac_addr[2] = z_readw(&regs->eth_hw_mac_hi2) >> 8;
	zz9000_mac_addr[3] = z_readw(&regs->eth_hw_mac_hi2) & 0xff;
	zz9000_mac_addr[4] = z_readw(&regs->eth_hw_mac_lo) >> 8;
	zz9000_mac_addr[5] = z_readw(&regs->eth_hw_mac_lo) & 0xff;

	printk(KERN_INFO "  ZZ9000 Ethernet device %s, MAC addr:  %02x:%02x:%02x:%02x:%02x:%02x\n",
	       dev->name,
	       zz9000_mac_addr[0], zz9000_mac_addr[1], zz9000_mac_addr[2],
	       zz9000_mac_addr[3], zz9000_mac_addr[4], zz9000_mac_addr[5]);

	ether_setup(dev);

	dev->netdev_ops = &zz9000_netdev_ops;
	dev->flags |= IFF_NOARP;
	dev->irq = IRQ_AMIGA_EXTER;
	memcpy(dev->dev_addr, zz9000_mac_addr, MAC_ADDR_LEN);

	stats = &dev->stats;
}

int zz9000_network_init(struct zz9000_platform_data *data)
{
	int ret = 0;

	zpd = data;

	/* Allocating the net device. */
	device = alloc_netdev(0, "eth%d", NET_NAME_UNKNOWN, zz9000_eth_setup);

	if ((ret = register_netdev(device))) {
		printk(KERN_EMERG
		       "zz900: net: error %i registering  device \"%s\"\n", ret,
		       device->name);
		free_netdev(device);
		return -1;
	}
	printk(KERN_INFO "Succeeded in loading %s!\n\n",
	       dev_name(&device->dev));

	return 0;
}

int zz9000_network_remove(struct zz9000_platform_data *data)
{
	if (device) {
		unregister_netdev(device);
		free_netdev(device);
		printk(KERN_INFO "zz9000: Network device removed.\n");
	}

	return 0;
}
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */

#include <linux/blk-mq.h>
#include <linux/console.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/string_helpers.h>
#include <uapi/linux/cdrom.h>
#include <uapi/linux/hdreg.h>
#include <asm/div64.h>
#include <asm/zorro.h>

#include "zz9000.h"

typedef struct {
	struct zz9000_platform_data *data;
	struct request_queue *queue;
	struct gendisk *disk;
	atomic_t open_counter;
	sector_t capacity;
	struct blk_mq_tag_set tag_set;
} zz9000_usb_storage_t;

static int zz9000_usb_open(struct block_device *bdev, fmode_t mode)
{
	zz9000_usb_storage_t *dev = bdev->bd_disk->private_data;
	if (dev == NULL) {
		printk(KERN_WARNING "zz9000: USB: Invalid disk private_data in zz9000_usb_open.\n");
		return -ENXIO;
	}

	atomic_inc(&dev->open_counter);

	printk(KERN_DEBUG "zz9000: USB: Device was opened\n");

	return 0;
}

static void zz9000_usb_release(struct gendisk *disk, fmode_t mode)
{
	zz9000_usb_storage_t *dev = disk->private_data;
	if (dev) {
		atomic_dec(&dev->open_counter);

		pr_debug("zz9000: USB: Device was closed\n");
	} else
		printk(KERN_WARNING "zz9000: USB: Invalid disk private_data in zz9000_usb_release.\n");
}

static int zz9000_usb_getgeo(zz9000_usb_storage_t * dev,
			     struct hd_geometry *geo)
{
	sector_t quotient;
	geo->start = 0;
	if (dev->capacity > 63) {
		geo->sectors = 63;
		quotient = div_u64((dev->capacity + (63 - 1)), 63);

		if (quotient > 255) {
			geo->heads = 255;
			geo->cylinders = (unsigned short)(div_u64((quotient + (255 - 1)), 255));
		} else {
			geo->heads = (unsigned char)quotient;
			geo->cylinders = 1;
		}
	} else {
		// Is this really needed?
		geo->sectors = (unsigned char)dev->capacity;
		geo->cylinders = 1;
		geo->heads = 1;
	}
	return 0;
}

static int zz9000_usb_ioctl(struct block_device *bdev, fmode_t mode,
			    unsigned int cmd, unsigned long arg)
{
	int ret = -ENOTTY;
	zz9000_usb_storage_t *dev = bdev->bd_disk->private_data;

	printk(KERN_WARNING "zz9000: USB: ioctl %x received\n", cmd);

	switch (cmd) {
	case HDIO_GETGEO:
		{
			struct hd_geometry geo;

			ret = zz9000_usb_getgeo(dev, &geo);
			if (copy_to_user
			    ((void *)arg, &geo, sizeof(struct hd_geometry)))
				ret = -EFAULT;
			else
				ret = 0;
			break;
		}

	case CDROM_GET_CAPABILITY:	/* 0x5331 - get capability */
		{
			struct gendisk *disk = bdev->bd_disk;

			if (bdev->bd_disk && (disk->flags & GENHD_FL_CD))
				ret = 0;
			else
				ret = -EINVAL;
			break;
		}

	default:
		{
			printk(KERN_WARNING
			       "zz9000: USB: ioctl %x not implemented\n", cmd);
			ret = -EINVAL;
		}
	}

	return ret;
}

#ifdef CONFIG_COMPAT
static int zz9000_usb_compat_ioctl(struct block_device *bdev, fmode_t mode,
				   unsigned int cmd, unsigned long arg)
{
	// CONFIG_COMPAT is to allow running 32-bit userspace code on a 64-bit kernel
	return -ENOTTY;		// not supported
}
#endif

static const struct block_device_operations zz9000_usb_fops = {
	.owner = THIS_MODULE,
	.open = zz9000_usb_open,
	.release = zz9000_usb_release,
	.ioctl = zz9000_usb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = zz9000_usb_compat_ioctl,
#endif
};

typedef struct {
	//nothing
} zz9000_usb_cmd_t;

static int zz9000_usb_read(zz9000_usb_storage_t *dev, void *b_buf, loff_t pos, unsigned long b_len)
{
	struct zz9000_platform_data *data = dev->data;
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *)data->registers;
	int ret = 0;

	pr_debug("read pos %lld len %ld\n", pos, b_len);

	z_writew(b_len >> SECTOR_SHIFT, &regs->usb_status);	//num_blocks;
	z_writew((pos >> SECTOR_SHIFT) >> 16, &regs->usb_rx_hi);
	z_writew((pos >> SECTOR_SHIFT) & 0xffff, &regs->usb_rx_lo);

	if (z_readw(&regs->usb_status) != (b_len >> SECTOR_SHIFT))
		printk(KERN_WARNING "zz9000: USB: unexpected read status=%d(%d)\n", regs->usb_status, (uint32_t)(b_len >> SECTOR_SHIFT));

	memcpy(b_buf, data->usb_mem, b_len);
	return ret;
}

static int zz9000_usb_write(zz9000_usb_storage_t *dev, void *b_buf, loff_t pos, unsigned long b_len)
{
	struct zz9000_platform_data *data = dev->data;
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *) data->registers;
	int ret = 0;

	pr_debug("write pos %lld len %ld\n", pos, b_len);

	memcpy(data->usb_mem, b_buf, b_len);

	z_writew(b_len >> SECTOR_SHIFT, &regs->usb_status);
	z_writew((pos >> SECTOR_SHIFT) >> 16, &regs->usb_tx_hi);
	z_writew((pos >> SECTOR_SHIFT) & 0xffff, &regs->usb_tx_lo);

	if (z_readw(&regs->usb_status) != (b_len >> SECTOR_SHIFT))
		printk(KERN_WARNING "zz9000: USB: unexpected write status=%d(%d)\n", regs->usb_status, (uint32_t)(b_len >> SECTOR_SHIFT));

	return ret;
}

static int zz9000_usb_request(struct request *rq, unsigned int *nr_bytes)
{
	int ret = 0;
	struct bio_vec bvec;
	struct req_iterator iter;
	zz9000_usb_storage_t *dev = rq->q->queuedata;
	loff_t pos = blk_rq_pos(rq) << SECTOR_SHIFT;
	loff_t dev_size = (loff_t) (dev->capacity << SECTOR_SHIFT);

	pr_debug("zz9000: USB: request start from sector %lld \n",
	       blk_rq_pos(rq));

	rq_for_each_segment(bvec, rq, iter) {
		unsigned long b_len;
		void *b_buf;

	        b_len = bvec.bv_len;
		b_buf = page_address(bvec.bv_page) + bvec.bv_offset;

		if ((pos + b_len) > dev_size)
			b_len = (unsigned long)(dev_size - pos);

		if (rq_data_dir(rq)) {
			pr_debug("zz9000: USB: Write\n");
			ret = zz9000_usb_write(dev, b_buf, pos, b_len);
		} else {
			pr_debug("zz9000: USB: Read\n");
			ret = zz9000_usb_read(dev, b_buf, pos, b_len);
		}
		pos += b_len;
		*nr_bytes += b_len;
	}

	return ret;
}

static blk_status_t zz9000_usb_queue_rq(struct blk_mq_hw_ctx *hctx,
					const struct blk_mq_queue_data *bd)
{
	blk_status_t status = BLK_STS_OK;
	struct request *rq = bd->rq;

	blk_mq_start_request(rq);

	{
		unsigned int nr_bytes = 0;
		if (zz9000_usb_request(rq, &nr_bytes) != 0)
			status = BLK_STS_IOERR;

		pr_debug("zz9000: USB: request process %d bytes\n", nr_bytes);

		if (blk_update_request(rq, status, nr_bytes))
			BUG();
		__blk_mq_end_request(rq, status);
	}

	return BLK_STS_OK;
}

static struct blk_mq_ops zz9000_mq_ops = {
	.queue_rq = zz9000_usb_queue_rq,
};

int zz9000_usb_major = 0;
static struct request_queue *zz9000_usb_queue;
static zz9000_usb_storage_t *zz9000_usb_storage;

int zz9000_usb_init(struct zz9000_platform_data *data)
{
	MNTZZ9KRegs *regs = (MNTZZ9KRegs *) data->registers;
	zz9000_usb_storage_t *dev;
	int ret = 0;
	int usb_block_size = 512;
	unsigned int usb_capacity;
	char cap_str_2[10], cap_str_10[10];

	usb_capacity = regs->usb_capacity;
	if (usb_capacity == 0) {
		printk(KERN_INFO "  USB device not found at boot.\n");
		return 0;
	}

	string_get_size(usb_capacity, usb_block_size,
			STRING_UNITS_2, cap_str_2, sizeof(cap_str_2));
	string_get_size(usb_capacity, usb_block_size,
			STRING_UNITS_10, cap_str_10, sizeof(cap_str_10));

	printk(KERN_INFO
	       "  USB capacity: %d %d-byte logical blocks: (%s GB/%s GiB)\n",
	       usb_capacity, usb_block_size, cap_str_10, cap_str_2);

	/* Register block device */
	if ((zz9000_usb_major = register_blkdev(0, "zzusb")) <= 0)
		return -EIO;

	dev = kzalloc(sizeof(zz9000_usb_storage_t), GFP_KERNEL);
	if (dev == NULL) {
		printk(KERN_WARNING
		       "zz9000: USB: Unable to allocate %zd bytes\n",
		       sizeof(zz9000_usb_storage_t));
		return -ENOMEM;
	}
	dev->data = data;
	zz9000_usb_storage = dev;

	/* Remember capacity in blocks */
	dev->capacity = usb_capacity;

	/* configure tag_set */
	dev->tag_set.cmd_size = sizeof(zz9000_usb_cmd_t);
	dev->tag_set.driver_data = dev;

	zz9000_usb_queue =
	    blk_mq_init_sq_queue(&dev->tag_set, &zz9000_mq_ops, 128,
				 BLK_MQ_F_SHOULD_MERGE);
	if (IS_ERR(zz9000_usb_queue)) {
		ret = PTR_ERR(zz9000_usb_queue);
		printk(KERN_WARNING
		       "zz9000: USB: Unable to allocate and initialize tag set\n");
		return -EIO;
	}
	dev->queue = zz9000_usb_queue;

	dev->queue->queuedata = dev;

	/* Set the hardware sector size and the max number of sectors */
	//blk_queue_hardsect_size(zz9000_usb_queue, usb_block_size);
	blk_queue_logical_block_size(zz9000_usb_queue, usb_block_size);
	blk_queue_max_hw_sectors(zz9000_usb_queue, 32);

	/* Allocate an associated gendisk */
	dev->disk = alloc_disk(16);
	if (dev->disk == NULL) {
		printk(KERN_WARNING "zz9000: USB: Failed to allocate disk\n");
		return -ENOMEM;
	}

	/* Fill in parameters associated with the gendisk */
	dev->disk->flags |= GENHD_FL_REMOVABLE;
	dev->disk->fops = &zz9000_usb_fops;
	dev->disk->queue = zz9000_usb_queue;
	dev->disk->major = zz9000_usb_major;
	dev->disk->first_minor = 0;
	dev->disk->private_data = dev;

	sprintf(dev->disk->disk_name, "zzusb");

	/* Set the capacity of USB storage media in number of sectors */
	set_capacity(dev->disk, usb_capacity);

	/* Add disk to the block I/O subsystem */
	add_disk(dev->disk);

	pr_debug("zz9000: USB block device was initialized.\n");

	return 0;
}

int zz9000_usb_remove(struct zz9000_platform_data *data)
{

	zz9000_usb_storage_t *dev = zz9000_usb_storage;

	if (dev == NULL)
		return 0;

	if (dev->disk)
		del_gendisk(dev->disk);

	if (dev->queue) {
		blk_cleanup_queue(dev->queue);
		dev->queue = NULL;
	}

	if (dev->tag_set.tags)
		blk_mq_free_tag_set(&dev->tag_set);

	if (dev->disk) {
		put_disk(dev->disk);
		dev->disk = NULL;
	}

	kfree(dev);
	zz9000_usb_storage = NULL;

	if (zz9000_usb_major > 0)
		unregister_blkdev(zz9000_usb_major, "zzusb");

	pr_debug("zz9000: USB block device was removed.\n");

	return 0;
}

[Index of Archives]     [Video for Linux]     [Yosemite News]     [Linux S/390]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux