diff options
Diffstat (limited to 'linux')
| -rw-r--r-- | linux/ch375lnx.h | 76 | ||||
| -rw-r--r-- | linux/chudisk.c | 622 | ||||
| -rw-r--r-- | linux/lnxabs.h | 139 | ||||
| -rw-r--r-- | linux/makefile | 28 | ||||
| -rwxr-xr-x | linux/mkdev.sh | 19 |
5 files changed, 884 insertions, 0 deletions
diff --git a/linux/ch375lnx.h b/linux/ch375lnx.h new file mode 100644 index 0000000..e9309a2 --- /dev/null +++ b/linux/ch375lnx.h @@ -0,0 +1,76 @@ +/* + * CHU - IO routines to CH375 from a Linux kernel module + * Copyright (C) 2026 Javier S. Pedro + * + * 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 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef CH375LNX_H +#define CH375LNX_H + +#include <linux/types.h> +#include <linux/ioport.h> +#include <asm/io.h> +#define _Packed __attribute__ ((packed)) +typedef enum { + false = 0, + true = 1 +} bool; + +#include <ch375.h> + +struct ch375 { + uint16_t io_base; + uint16_t sec_size; +}; + +static inline uint8_t ch375_read_data(struct ch375 *dev) +{ + return inb(dev->io_base + CH375_PORT_DATA); +} + +static inline void ch375_write_data(struct ch375 *dev, uint8_t data) +{ + outb(data, dev->io_base + CH375_PORT_DATA); +} + +static inline void ch375_send_command(struct ch375 *dev, enum ch375_command cmd) +{ + outb(cmd, dev->io_base + CH375_PORT_CMD); +} + +/// True if interrupt line is currently active (i.e. low) +static inline bool ch375_poll_interrupt(struct ch375 *dev) +{ + return !(inb(dev->io_base + CH375_PORT_CMD) & 0x80); +} + +/** Reads one transfer block (64 bytes max). */ +static uint8_t ch375_read_data_block(struct ch375 *dev, uint8_t *buffer) +{ + uint8_t n = ch375_read_data(dev); + + insb(dev->io_base + CH375_PORT_DATA, buffer, n); + + return n; +} + +static void ch375_write_data_block(struct ch375 *dev, uint8_t * buffer, uint8_t n) +{ + ch375_write_data(dev, n); + + outsb(dev->io_base + CH375_PORT_DATA, buffer, n); +} + +#endif // CH375LNX_H diff --git a/linux/chudisk.c b/linux/chudisk.c new file mode 100644 index 0000000..31dd9e5 --- /dev/null +++ b/linux/chudisk.c @@ -0,0 +1,622 @@ +/* + * CHU - Module for driving CH375 in disk mode from Linux 2.0/2.2 + * Copyright (C) 2026 Javier S. Pedro + * + * 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. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/** how many different units we can handle, i.e. /dev/chuda, chudb, etc.*/ +#define CHUDISK_MAX_UNITS 1 +/** how many bits of the minor number are dedicated to partition number */ +#define CHUDISK_MINOR_SHIFT 6 +/** how many different partitions we can handle (/dev/chuda1, chuda2, etc.) + * which comes from the number of bits available in the minor number. */ +#define CHUDISK_MAX_PARTITIONS (1 << CHUDISK_MINOR_SHIFT) +/** total number of minors we can handle. */ +#define CHUDISK_MAX_MINORS (CHUDISK_MAX_UNITS << CHUDISK_MINOR_SHIFT) + +/* module parameters */ +/** configurable major number */ +/* by default, reuse the one from Mylex DAC960 PCI RAID controller; sixth controller" */ +static short major = 53; +/** IO base */ +static short io = 0x260; +/** skip device proving */ +static char force = 0; +/** configurable block size */ +static int softblk = 1024; + +/* for blk.h: (needs to be defined before blk.h/lnxabs.h is included) */ +#define MAJOR_NR major +#define DEVICE_NAME "CH375" +#define DEVICE_REQUEST do_chudisk_request +#define DEVICE_NR(device) (MINOR(device)>>CHUDISK_MINOR_SHIFT) +#define DEVICE_ON(device) +#define DEVICE_OFF(device) + +#include "lnxabs.h" + +#include "ch375lnx.h" +#include <scsi.h> + +#define ENABLE_DLOG 0 +#if ENABLE_DLOG +#define dprintf(args...) printk(KERN_DEBUG args) +#else +#define dprintf(args...) /**/ +#endif + +MODULE_PARM(major, "h"); +MODULE_PARM_DESC(major, "major device number"); + +MODULE_PARM(io, "h"); +MODULE_PARM_DESC(io, "io base"); + +MODULE_PARM(force, "b"); +MODULE_PARM_DESC(force, "force loading, skip probing"); + +MODULE_PARM(softblk, "i"); +MODULE_PARM_DESC(softblk, "\"soft\" block size in bytes"); + +MODULE_DESCRIPTION("CHUDISK CH375 USB disk driver"); +MODULE_LICENSE("GPL"); + +/** hd_structs, used by gendisk to keep track of partition sizes, start points, etc. */ +static struct hd_struct chudisk_structs[CHUDISK_MAX_MINORS]; +/** used by blk layer to keep track of size of every (minor) blk device. */ +static int chudisk_sizes[CHUDISK_MAX_MINORS] = {0}; +/** the block size (possibly different for every minor, but we really don't do that) */ +static int chudisk_blksizes[CHUDISK_MAX_MINORS] = {0}; +/** hardware block size */ +static int chudisk_hardsectsizes[CHUDISK_MAX_MINORS] = {0}; +/** count open for each unit (not partition) */ +static int chudisk_open_count[CHUDISK_MAX_UNITS] = {0}; +/** driver data for each unit */ +static struct ch375 chudisk_dev[CHUDISK_MAX_UNITS]; + +/** main gendisk layer data structure, which handles partioning */ +static struct gendisk chudisk_gendisk = { + .major_name = "chud", + .minor_shift = CHUDISK_MINOR_SHIFT, + .max_p = CHUDISK_MAX_PARTITIONS, + .max_nr = CHUDISK_MAX_UNITS, + .part = chudisk_structs, + .sizes = chudisk_sizes +}; + +/** Waits for the CH375 to generate an interrupt, returns result value. + * @param timeout max jiffies to wait for an interrupt */ +static uint8_t wait_for_ch375_result(struct ch375 *dev, unsigned int timeout) +{ + unsigned long start = jiffies; + unsigned long expire = start + timeout; + + while (!ch375_poll_interrupt(dev) && time_before(jiffies, expire)) { + + } + + if (ch375_poll_interrupt(dev)) { + uint8_t status = ch375_get_int_status(dev); + return status; + } else { + printk(KERN_WARNING "chudisk: interrupt timeout! waited %lu jiffies\n", jiffies - start); + return 0; + } +} + +/** Gets (error) sense data from the USB device (if any) + * and prints it to the log. */ +static void disk_sense_error(struct ch375 *dev) +{ + char buffer[CH375_BLOCK_SIZE]; + unsigned len; + + ch375_send_command(dev, CH375_DISK_R_SENSE); + if (wait_for_ch375_result(dev, 1000) != CH375_INT_USB_SUCCESS) { + printk(KERN_NOTICE "chudisk: could not sense error\n"); + return; + } + + ch375_send_command(dev, CH375_RD_USB_DATA); + len = ch375_read_data_block(dev, &buffer[0]); + + printk(KERN_NOTICE "chudisk: sense read data block len = %d\n", len); + printk(KERN_NOTICE "chudisk: sense packet %hx %hx %hx %hx %hx %hx %hx %hx\n", + buffer[0], buffer[1], buffer[2], buffer[3], + buffer[4], buffer[5], buffer[6], buffer[7]); + printk(KERN_NOTICE "chudisk: %hx %hx %hx %hx %hx %hx %hx %hx\n", + buffer[8], buffer[9], buffer[10], buffer[11], + buffer[12], buffer[13], buffer[14], buffer[15]); +} + +/** Read N sectors from the disk + * @return number of sectors read + */ +static unsigned disk_read_sectors(struct ch375 *dev, uint8_t *buffer, uint32_t lba, uint8_t num) +{ + uint8_t result; + unsigned total = 0; // Total bytes written + + dprintf("disk read buf=%p sector=%lu num=%hu\n", buffer, lba, num); + + // Initiate read operation + ch375_disk_read(dev, lba, num); + + // Now keep going as long as ch375 says to keep going + while ((result = wait_for_ch375_result(dev, 1000)) == CH375_INT_USB_DISK_READ) { + unsigned len; + + // Sanity check + if (total / dev->sec_size >= num) { + dprintf("read overflow\n"); + break; + } + + ch375_send_command(dev, CH375_RD_USB_DATA); + len = ch375_read_data_block(dev, &buffer[total]); + + //dprintf(" disk read data block len = %d\n", len); + + if (len == 0) { + // Done? + break; + } + + total += len; + + // Continue + ch375_send_command(dev, CH375_DISK_RD_GO); + } + + dprintf(" disk read total bytes = %u, sectors = %u, final status = 0x%hx\n", + total, total / dev->sec_size, result); + + if (result == CH375_INT_USB_DISK_ERR) { + disk_sense_error(dev); + } + + return total / dev->sec_size; +} + +/** Write N sectors to the disk + * @return number of sectors written + */ +static unsigned disk_write_sectors(struct ch375 *dev, uint8_t *buffer, uint32_t lba, uint8_t num) +{ + uint8_t result; + unsigned total = 0; // Total bytes written + + dprintf("disk write buf=%p sector=%lu num=%hu\n", buffer, lba, num); + + // Initiate operation + ch375_disk_write(dev, lba, num); + + // Now keep going as long as ch375 says to keep going + while ((result = wait_for_ch375_result(dev, 1000)) == CH375_INT_USB_DISK_WRITE) { + const unsigned len = CH375_BLOCK_SIZE; + + // Sanity check + if (total / dev->sec_size >= num) { + dprintf("write overflow\n"); + break; + } + + ch375_send_command(dev, CH375_WR_USB_DATA7); + ch375_write_data_block(dev, &buffer[total], len); + + total += len; + + // Continue + ch375_send_command(dev, CH375_DISK_WR_GO); + } + + dprintf(" disk write total bytes = %u, sectors = %u, final status = 0x%hx\n", + total, total / dev->sec_size, result); + + if (result == CH375_INT_USB_DISK_ERR) { + disk_sense_error(dev); + } + + return total / dev->sec_size; +} + +/** Enables CH375 USB host mode, initializes "disk" mode on it + * and connects to a disk. + * @return true if connected + */ +static bool chudisk_connect(struct ch375 *dev) +{ + int tries; + + dprintf("switch to usb host\n"); + ch375_set_usb_mode(dev, CH375_USB_MODE_HOST_AUTO_SOF); + if (wait_for_ch375_result(dev, HZ / 5) != CH375_INT_USB_CONNECT) { + printk(KERN_INFO "chudisk: could not turn on usb host, likely no device\n"); + return false; + } + + dprintf("try init disk\n"); + ch375_disk_init(dev); + if (wait_for_ch375_result(dev, HZ) != CH375_INT_USB_SUCCESS) { + printk(KERN_INFO "chudisk: could not init disk\n"); + return false; + } + + // looks like we have to keep polling by asking "is the disk ready?" + // even if each time we ask we are already polling + tries = 3; + while (tries-- > 0) { + ch375_test_disk_ready(dev); + if (wait_for_ch375_result(dev, HZ) == CH375_INT_USB_SUCCESS) { + dprintf("disk now init\n"); + return true; + } + mdelay(50); + } + + printk(KERN_INFO "chudisk: disk failed to init\n"); + return false; +} + +/** Tests if there is still a disk connected and initialized. */ +static bool is_chudisk_ready(struct ch375 *dev) +{ + ch375_test_disk_ready(dev); + if (wait_for_ch375_result(dev, HZ/10) == CH375_INT_USB_SUCCESS) { + return true; + } else { + return false; + } +} + +/** Actual main I/O function, called whenever there are pending I/O requests */ +static void do_chudisk_request(void) +{ + while (CURRENT) { + int rc = 0; + struct hd_struct *hd_struct; + INIT_REQUEST; + + hd_struct = &chudisk_structs[MINOR(CURRENT->rq_dev)]; + + dprintf("do_chudisk_request: cmd %u sector %lu(/%lu) nr_sectors %lu/%lu\n", + CURRENT->cmd, CURRENT->sector, hd_struct->nr_sects, CURRENT->current_nr_sectors, CURRENT->nr_sectors); + + // If: + // the request is for one of our devices + // and it is in-range of the corresponding partition + // and respects our max_nr_sectors (256) + + if (CURRENT_DEV < CHUDISK_MAX_UNITS + && CURRENT->sector + CURRENT->nr_sectors <= hd_struct->nr_sects + && CURRENT->current_nr_sectors <= 0xff) { + struct ch375 *dev = &chudisk_dev[CURRENT_DEV]; + uint32_t sector = CURRENT->sector + hd_struct->start_sect; + uint8_t cnt = CURRENT->current_nr_sectors; + + switch (CURRENT->cmd) { + case READ: + if (disk_read_sectors(dev, CURRENT->buffer, sector, cnt) == cnt) { + rc = true; // success + } + break; + case WRITE: + if (disk_write_sectors(dev, CURRENT->buffer, sector, cnt) == cnt) { + rc = true; // success + } + break; + default: + break; + } + } + + end_request(rc); + } +} + +/** Called by gendisk to check if removable media may have changed since last time. + * If so, gendisk will invalidate buffers and eventually call revalidate. + * @return true if media may have changed + */ + static int chudisk_check_media_change(kdev_t dev) +{ + int unit = DEVICE_NR(dev); + bool ready; + + dprintf("chudisk_check_media_change: unit=%d\n", unit); + + if (unit >= CHUDISK_MAX_UNITS) { + return -ENODEV; + } + + ready = is_chudisk_ready(&chudisk_dev[unit]); + + dprintf("chudisk_check_media_change: unit=%d disk_ready=%d\n", unit, ready); + + // if CH375 is not ready, it means device was unplugged (or never configured to begin with) + // assume media changed + return !ready; +} + +/** Revalidate means drop all buffers (assuming media changed) + * reinitialize all media information (sizes, etc.) + * and re-read partition tables. */ +static int chudisk_revalidate(kdev_t devno) +{ + int unit = DEVICE_NR(devno); + struct ch375 *dev = &chudisk_dev[unit]; + uint32_t blk_size; + uint32_t sector_cnt; + char buffer[CH375_BLOCK_SIZE]; + int p; + + dprintf("chudisk_revalidate: unit=%d\n", unit); + + if (unit >= CHUDISK_MAX_UNITS) { + return -ENODEV; + } + + if (!is_chudisk_ready(dev)) { + if (!chudisk_connect(dev)) { + return -ENOMEDIUM; + } + } + + ch375_send_command(dev, CH375_DISK_SIZE); + if (wait_for_ch375_result(dev, HZ / 10) != CH375_INT_USB_SUCCESS) { + return -EIO; + } + + ch375_send_command(dev, CH375_RD_USB_DATA); + if (ch375_read_data_block(dev, buffer) < 8) { + printk(KERN_WARNING "chudisk: disk size command return less than 8 bytes"); + return -EIO; + } + + blk_size = ntohl(*(uint32_t*)&buffer[4]); + sector_cnt = ntohl(*(uint32_t*)&buffer[0]); + + dprintf("chudisk: sector size %u num sectors %u\n", blk_size, sector_cnt); + + // TODO Support non-default block_size + dev->sec_size = blk_size; + ch375_write_reg(dev, CH375_REG_DISK_PKT_P_SEC, blk_size / CH375_BLOCK_SIZE); + + // Fill in the struct for the raw device, so that we can answer I/O requests on it + chudisk_structs[unit << CHUDISK_MINOR_SHIFT].nr_sects = sector_cnt; + + // For all minors associated to this unit + for (p = 0; p < CHUDISK_MAX_PARTITIONS; p++) { + int minor = (unit << CHUDISK_MINOR_SHIFT) + p; + kdev_t mdevno = MKDEV(MAJOR_NR, minor); + + fsync_dev(mdevno); + invalidate_inodes_dev(mdevno); + invalidate_buffers(mdevno); + + chudisk_hardsectsizes[minor] = blk_size; + } + + resetup_one_dev(&chudisk_gendisk, unit); + + return 0; +} + +/** Fill in guessed hd_geometry from the current media size. */ +static int chudisk_get_geom(kdev_t devno, struct hd_geometry *g) +{ + struct hd_struct *hd_struct = &chudisk_structs[DEVICE_NR(devno)]; + struct hd_struct *hd_struct_part = &chudisk_structs[MINOR(devno)]; + unsigned int cylinders, heads, sectors; + + if (hd_struct->nr_sects == 0 || hd_struct_part->nr_sects == 0) { + return -ENOMEDIUM; + } + + guess_geometry(hd_struct->nr_sects, &cylinders, &heads, §ors); + g->cylinders = cylinders > 0xFFFF ? 0xFFFF : cylinders; + g->heads = heads; + g->sectors = sectors; + g->start = hd_struct_part->start_sect; + + return 0; +} + +/** Ioctl handling. */ +static int chudisk_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int err; + + dprintf("chudisk_ioctl: unit=%d cmd=%u arg=%lu\n", DEVICE_NR(inode->i_rdev), cmd, arg); + + if (DEVICE_NR(inode->i_rdev) >= CHUDISK_MAX_UNITS) { + return -ENXIO; + } + + switch (cmd) { + case HDIO_GETGEO: { + struct hd_geometry g; + if (!arg) return -EINVAL; + if ((err = chudisk_get_geom(inode->i_rdev, &g))) { + return err; + } + return copy_to_user((struct hd_geometry*)arg, &g, sizeof g) ? -EFAULT : 0; + } + case BLKRASET: + if (!is_sys_admin()) return -EACCES; + if (arg > 0xff) return -EINVAL; + read_ahead[MAJOR(inode->i_rdev)] = arg; + return 0; + case BLKRAGET: + if (!arg) return -EINVAL; + return put_user(read_ahead[MAJOR(inode->i_rdev)], (long *)arg); + case BLKGETSIZE: + if (!arg) return -EINVAL; + return put_user(chudisk_structs[MINOR(inode->i_rdev)].nr_sects, (long*)arg); + case BLKFLSBUF: + if (!is_sys_admin()) return -EACCES; + fsync_dev(inode->i_rdev); + invalidate_buffers(inode->i_rdev); + return 0; + case BLKRRPART: + if (!is_sys_admin()) return -EACCES; + return chudisk_revalidate(inode->i_rdev); + RO_IOCTLS(inode->i_rdev, arg); + default: + return -EINVAL; + } +} + +static int chudisk_open(struct inode *inode, struct file *file) +{ + int unit = DEVICE_NR(inode->i_rdev); + dprintf("chudisk_open: unit=%d\n", unit); + if (unit >= CHUDISK_MAX_UNITS) { + return -ENXIO; + } + + MOD_INC_USE_COUNT; + + if (chudisk_open_count[unit] == 0) { + // First time open + dprintf("chudisk: first open\n"); + // Ask gendisk to check if media may have changed (which may call revalidate) + check_disk_change(inode->i_rdev); + // if we still have no media, refuse open() call + if (!is_chudisk_ready(&chudisk_dev[unit])) { + MOD_DEC_USE_COUNT; + return -ENOMEDIUM; + } + } + + chudisk_open_count[unit]++; + + return 0; +} + +static int chudisk_release(struct inode *inode, struct file *file) +{ + int unit = DEVICE_NR(inode->i_rdev); + + dprintf("chudisk_release: unit=%d\n", unit); + + if (unit >= CHUDISK_MAX_UNITS) { + return -ENXIO; + } + + chudisk_open_count[unit]--; + MOD_DEC_USE_COUNT; + + return 0; +} + +static struct file_operations blk_fops = { + .read = block_read, + .write = block_write, + .ioctl = chudisk_ioctl, + .open = chudisk_open, + .release = chudisk_release, + .check_media_change = chudisk_check_media_change, + .revalidate = chudisk_revalidate +}; + +static __init int chudisk_init(void) +{ + // TODO Here we are currently assuming one unit + // But it would be trivial to add support for more. + const int unit = 0; + struct ch375 *dev = &chudisk_dev[unit]; + int i; + + dev->io_base = io; + dev->sec_size = 512; + + if (check_region(io, 2)) { + printk(KERN_WARNING "chudisk: ports at 0x%x not available\n", io); + return -ENXIO; + } + + request_region(io, 2, "chudisk"); + + if (!force) { + dprintf("chudisk: resetting CH375 at 0x%hx\n", dev->io_base); + ch375_reset(dev); + mdelay(100); + if (ch375_check_exists(dev, 0xA5) != (uint8_t)~0xA5) { + printk(KERN_WARNING "chudisk: CH375 not detected at 0x%hx (failed CHECK_EXISTS), change io= param\n", dev->io_base); + release_region(io, 2); + return -ENODEV; + } + } + + if (register_blkdev(MAJOR_NR, "chud", &blk_fops)) { + printk(KERN_WARNING "chudisk: unable to get major number %d\n", MAJOR_NR); + release_region(io, 2); + return -ENXIO; + } + + printk(KERN_INFO "chudisk: found CH375 v.%x at 0x%hx, using major %d\n", + ch375_get_version(dev), dev->io_base, MAJOR_NR); + + // Set defaults for unit + for (i = 0; i < CHUDISK_MAX_PARTITIONS; i++) { + const int minor = (unit << CHUDISK_MINOR_SHIFT) + i; + chudisk_sizes[minor] = 0; + chudisk_blksizes[minor] = softblk; + chudisk_hardsectsizes[minor] = dev->sec_size; + } + + // Set defaults for everything else + read_ahead[MAJOR_NR] = 8; // default to 8 sector read-ahead + + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + blk_size[MAJOR_NR] = chudisk_sizes; + blksize_size[MAJOR_NR] = chudisk_blksizes; + hardsect_size[MAJOR_NR] = chudisk_hardsectsizes; + + chudisk_gendisk.major = major; + chudisk_gendisk.nr_real = 1; + chudisk_gendisk.next = gendisk_head; + gendisk_head = &chudisk_gendisk; + + chudisk_revalidate(MKDEV(MAJOR_NR, unit << CHUDISK_MINOR_SHIFT)); + + return 0; +} + +static void chudisk_exit(void) +{ + struct gendisk **gdp; + + unregister_blkdev(MAJOR_NR, "chud"); + release_region(io, 2); + + for(gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) { + if (*gdp == &chudisk_gendisk) { + *gdp = (*gdp)->next; + break; + } + } + + blk_dev[MAJOR_NR].request_fn = NULL; + blk_size[MAJOR_NR] = NULL; + blksize_size[MAJOR_NR] = NULL; + hardsect_size[MAJOR_NR] = NULL; + + dprintf("chudisk: bye\n"); +} + + +module_init(chudisk_init); +module_exit(chudisk_exit); diff --git a/linux/lnxabs.h b/linux/lnxabs.h new file mode 100644 index 0000000..47adcea --- /dev/null +++ b/linux/lnxabs.h @@ -0,0 +1,139 @@ +/* + * Abstractions between different versions of Linux + * Currently only tested amonst the latest versions of 2.0 and 2.2 + * Copyright (C) 2026 Javier S. Pedro + * + * 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. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif + +#include <linux/version.h> + +#define MAKE_VERSION_CODE(vers, rel, seq) ( (vers)<<16 | (rel)<<8 | (seq) ) + +#include <linux/kernel.h> +#include <linux/module.h> + +// Module entry point abstraction +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,36) + #define __init /**/ + #define module_init(x) int init_module(void) { return x(); } + #define module_exit(x) void cleanup_module(void) { x(); } +#else + #include <linux/init.h> +#endif + +// Module params +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,26) + #define MODULE_PARM(var, type) /**/ + #define MODULE_PARM_DESC(var, s) /**/ + #define MODULE_DESCRIPTION(s) /**/ +#endif +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,4,0) + #define MODULE_LICENSE(s) /**/ +#endif + +#include <linux/ioport.h> +#include <linux/fs.h> +#include <linux/errno.h> + +#include <linux/blk.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> + +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0) +#define invalidate_inodes_dev(devno) invalidate_inodes(devno) +#else +static inline void invalidate_inodes_dev(kdev_t devno) { + struct super_block *sb = get_super(devno); + if (sb) invalidate_inodes(sb); +} +#endif + +#include <linux/delay.h> + +// time before/after +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,95) + #define time_after(a,b) ((long)(b) - (long)(a) < 0) + #define time_before(a,b) time_after(b,a) +#endif + +// mdelay +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0) +static inline void mdelay(int milliseconds) +{ + int i; + for(i=0; i<milliseconds; i++) + udelay(1000); +} +#endif + +// put_user/copy_to_user +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0) + #undef put_user + #undef get_user + + #define put_user(x,ptr) ({ \ + int i = verify_area(VERIFY_WRITE, (ptr), sizeof(*(ptr))); \ + if (i == 0) { __put_user((unsigned long)(x),(ptr),sizeof(*(ptr))); } \ + i; \ + }) + #define get_user(x,ptr) ({ \ + int i = verify_area(VERIFY_READ, (ptr), sizeof(*(ptr))); \ + if (i == 0) { x = ((__typeof__(*(ptr)))__get_user((ptr),sizeof(*(ptr)))); } \ + i; \ + }) + + extern inline int copy_from_user(void *to, const void *from, unsigned long n) + { + int i = verify_area(VERIFY_READ, from, n); + if (i) + return i; + memcpy_fromfs(to, from, n); + return 0; + } + + extern inline int copy_to_user(void *to, const void *from, unsigned long n) + { + int i = verify_area(VERIFY_WRITE, to, n); + if (i) + return i; + memcpy_tofs(to, from, n); + return 0; + } +#else + #include <asm/uaccess.h> +#endif + +// capabilities +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0) +#define is_sys_admin() suser() +#else +#define is_sys_admin() capable(CAP_SYS_ADMIN) +#endif + +// stdtypes +#if LINUX_VERSION_CODE < MAKE_VERSION_CODE(2,1,0) + typedef __s8 int8_t; + typedef __s16 int16_t; + typedef __s32 int32_t; + typedef __u8 uint8_t; + typedef __u16 uint16_t; + typedef __u32 uint32_t; +#endif + +#include <asm/byteorder.h> diff --git a/linux/makefile b/linux/makefile new file mode 100644 index 0000000..f293cc1 --- /dev/null +++ b/linux/makefile @@ -0,0 +1,28 @@ +MODDIR := /lib/modules/$(shell uname -r) + +INCLUDE := -isystem $(MODDIR)/build/include -isystem ../inc +CFLAGS := -O2 -D__KERNEL__ -DMODULE -Wall $(INCLUDE) +ifneq (,$(wildcard $(MODDIR)/build/include/linux/modversions.h)) +CFLAGS += -DMODVERSIONS +endif + +CC ?= gcc + +chudisk.o: chudisk.c lnxabs.h ch375lnx.h + +.PHONY: clean install uninstall test +clean: + rm -rf *.o + +install: chudisk.o + install -m0644 chudisk.o $(MODDIR)/block + depmod -A + +uninstall: + rm -f $(MODDIR)/block/chudisk.o + +test: chudisk.o + if lsmod | grep chudisk; then rmmod chudisk; fi + dmesg -n 8 + dmesg -c > /dev/null + insmod chudisk.o force=1 diff --git a/linux/mkdev.sh b/linux/mkdev.sh new file mode 100755 index 0000000..d1393e6 --- /dev/null +++ b/linux/mkdev.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +major=53 +if [ $# -gt 0 ]; then + major=$1 +fi + +prefix=/dev/chuda +if [ $# -gt 1 ]; then + prefix=$2 +fi + +rm -f ${prefix}* +mknod "${prefix}" b $major 0 +for i in $(seq 1 20); do + mknod "${prefix}${i}" b $major $i +done + +chown root:disk ${prefix}* |
