2006-09-08 17:47:54

by Thomas Maier

[permalink] [raw]
Subject: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Hello

(3th try with fixed inline patch)

This is a patch for the packet writing driver pktcdvd.
It adds a sysfs interface to the driver and a bio write
queue "congestion" handling.

The patch modifies following files of the Linux 2.6.18-rc6
source tree:
Documentation/cdrom/packet-writing.txt
Documentation/ABI/testing/sysfs-block-pktcdvd
include/linux/pktcdvd.h
drivers/block/pktcdvd.c
drivers/block/Kconfig
block/genhd.c

(genhd.c must be changed to export the block_subsys
symbol)

The bio write queue changes are in pktcdvd.c in functions:
pkt_make_request()
pkt_bio_finished()

Any comments and improvements are welcomed ;)


Why this patch?
===============
This driver uses an internal bio write queue to store
write requests from the block layer, passed to the driver
over its own make_request function.
I am using Linux 2.6.17 on an Athlon 64 X2, 2G RAM and while
writing huge files (>200M) to a DVDRAM using the pktcdvd driver,
the bio write queue raised >200000 entries! This led to
kernel out of memory Oops! e.g.:

----------------------------------------------------------
Aug 14 17:42:26 master vmunix: pktcdvd: 4473408kB available on disc
Aug 14 17:42:54 master vmunix: pktcdvd: write speed 4155kB/s
Aug 14 17:54:24 master vmunix: oom-killer: gfp_mask=0xd0, order=1
Aug 14 17:54:24 master vmunix: <c014346f> out_of_memory+0x12f/0x150
<c01452d0> __alloc_pages+0x280/0x2e0
Aug 14 17:54:24 master vmunix: <c015a52a> cache_alloc_refill+0x2ea/0x500
<c015a7a1> __kmalloc+0x61/0x70
Aug 14 17:54:24 master vmunix: <c039c0b3> __alloc_skb+0x53/0x110
<c03985b6> sock_alloc_send_skb+0x176/0x1c0
Aug 14 17:54:24 master vmunix: <c0399c5b> sock_def_readable+0x7b/0x80
<c041262b> unix_stream_sendmsg+0x1cb/0x310
Aug 14 17:54:24 master vmunix: <c039502b> do_sock_write+0xab/0xc0
<c0395720> sock_aio_write+0x80/0x90
Aug 14 17:54:24 master vmunix: <c011a609> __wake_up_common+0x39/0x60
<c015d984> do_sync_write+0xc4/0x100
Aug 14 17:54:47 master vmunix: printk: 10 messages suppressed.
Aug 14 17:54:47 master vmunix: oom-killer: gfp_mask=0xd0, order=0
Aug 14 17:54:47 master vmunix: <c014346f> out_of_memory+0x12f/0x150
<c01452d0> __alloc_pages+0x280/0x2e0
Aug 14 17:54:47 master vmunix: <c0258de2> __next_cpu+0x12/0x30
<c015a52a> cache_alloc_refill+0x2ea/0x500
Aug 14 17:54:47 master vmunix: <c015a23a> kmem_cache_alloc+0x4a/0x50
<c03987ea> sk_alloc+0x2a/0x150
Aug 14 17:54:47 master vmunix: <c03e3f8d> inet_create+0xed/0x320
<c03950a2> sock_alloc_inode+0x12/0x70
Aug 14 17:54:47 master vmunix: <c017790e> alloc_inode+0xce/0x180
<c03966f3> __sock_create+0x123/0x2f0
Aug 14 17:54:49 master vmunix: Total swap = 2152668kB
Aug 14 17:54:49 master vmunix: Free swap: 2152436kB
Aug 14 17:54:49 master vmunix: 524272 pages of RAM
Aug 14 17:54:49 master vmunix: 294896 pages of HIGHMEM
Aug 14 17:54:49 master vmunix: 5767 reserved pages
Aug 14 17:54:49 master vmunix: 238277 pages shared
Aug 14 17:54:49 master vmunix: 35 pages swap cached
Aug 14 17:54:49 master vmunix: 47682 pages dirty
Aug 14 17:54:49 master vmunix: 157861 pages writeback
Aug 14 17:54:49 master vmunix: 17359 pages mapped
Aug 14 17:54:49 master vmunix: 23835 pages slab
Aug 14 17:54:49 master vmunix: 176 pages pagetables
Aug 14 17:54:59 master vmunix: <c0145355> __get_free_pages+0x25/0x40
Aug 14 17:55:19 master vmunix: 294896 pages of HIGHM<6>5767 reserved pages
------------------------------------------------------------

It don't know exactly what is wrong in the kernel, but
it seems it must be something with the kernels memory handling.

To be able to use the pktcdvd driver now, i created this patch.
It simply limits the size of the bio write queue of the driver
to save kernel memory. Does not cure the "kernel bug", but the
symptom ;)
If the number of bio write requests would raise the bio
queue size over a high limit (congestion on), the
make_request function waits till the worker thread has
lowered the queue size below the "congestion off" mark.
The wait is similar to the wait in get_request_wait(),
called by the "normal" request function __make_request().

Also there is now a sysfs interface for the driver and the
procfs interface can be switched of by a kernel config
parameter. See the file
Documentation/ABI/testing/sysfs-block-pktcdvd
in this patch.

-Thomas Maier



---[snip]-----------------------------
diff -urpN linux-2.6.18-rc6/Documentation/ABI/testing/sysfs-block-pktcdvd pktcdvd-patch-2.6.18-rc6/Documentation/ABI/testing/sysfs-block-pktcdvd
--- linux-2.6.18-rc6/Documentation/ABI/testing/sysfs-block-pktcdvd 1970-01-01 01:00:00.000000000 +0100
+++ pktcdvd-patch-2.6.18-rc6/Documentation/ABI/testing/sysfs-block-pktcdvd 2006-09-05 20:29:41.000000000 +0200
@@ -0,0 +1,84 @@
+What: /sys/block/pktcdvd/
+Date: Sep. 2006
+KernelVersion: 2.6.18
+Contact: Thomas Maier <[email protected]>
+Description:
+
+sysfs interface
+---------------
+
+The pktcdvd module (packet writing driver) creates
+following files in the sysfs:
+
+( <pktdevname> is one of pktcdvd0..pktcdvd7 )
+( <devid> is in format major:minor )
+
+/sys/block/pktcdvd/
+ add (w) Write a block device id to create a
+ new pktcdvd device and map it to the
+ block device.
+
+ remove (w) Write the pktcdvd device id or the
+ mapped block device id to it, to
+ remove the pktcdvd device.
+
+ device_map (r) Shows the device mapping in format:
+ <pktdevname> <pktdevid> <blkdevid>
+
+ packet_buffers (rw) Number of concurrent packets per
+ pktcdvd device. Used for new created
+ devices.
+
+
+/sys/block/pktcdvd/<pktdevname>/packet/
+ statistic (r) Show device statistic. One line with
+ 5 values in following order:
+ packets-started
+ packets-end
+ written in kB
+ read gather in kB
+ read in kB
+
+ reset_statistic (w) Write any value to it to reset
+ pktcdvd device statistic values, like
+ bytes read/written.
+
+ info (r) Lots of user readable driver statistics
+ and infos. Multiple lines!
+
+ write_queue_size (r) Contains the size of the bio write
+ queue.
+
+ write_congestion_off (rw) If bio write queue size is below
+ this mark, accept new bio requests
+ from the block layer.
+
+ write_congestion_on (rw) If bio write queue size is higher
+ as this mark, do no longer accept
+ bio write requests from the block
+ layer and wait till the pktcdvd
+ device has processed enough bio's
+ so that bio write queue size is
+ below congestion off mark.
+
+ mapped_to Symbolic link to mapped block device
+ in the /sys/block tree.
+
+
+Example:
+--------
+To use the pktcdvd sysfs interface directly, you can do:
+
+# create a new pktcdvd device mapped to /dev/hdc
+echo "22:0" >/sys/block/pktcdvd/add
+cat /sys/block/pktcdvd/device_map
+# assuming device pktcdvd0 was created, look at stat's
+cat /sys/block/pktcdvd/pktcdvd0/packet/stat
+# print the device id of the mapped block device
+cat /sys/block/pktcdvd/pktcdvd0/packet/mapped_to/dev
+# similar to
+fgrep pktcdvd0 /sys/block/pktcdvd/device_map
+# remove device, using pktcdvd0 device id 253:0
+echo "253:0" >/sys/block/pktcdvd/remove
+# same as using the mapped block device id 22:0
+echo "22:0" >/sys/block/pktcdvd/remove
diff -urpN linux-2.6.18-rc6/Documentation/cdrom/packet-writing.txt pktcdvd-patch-2.6.18-rc6/Documentation/cdrom/packet-writing.txt
--- linux-2.6.18-rc6/Documentation/cdrom/packet-writing.txt 2006-09-04 21:24:20.000000000 +0200
+++ pktcdvd-patch-2.6.18-rc6/Documentation/cdrom/packet-writing.txt 2006-09-05 20:29:41.000000000 +0200
@@ -1,13 +1,15 @@
Getting started quick
---------------------

-- Select packet support in the block device section and UDF support in
- the file system section.
+- Select packet support in the block device section (and enable
+ the pktcdvd procfs interface) and UDF support in the file
+ system section.

- Compile and install kernel and modules, reboot.

- You need the udftools package (pktsetup, mkudffs, cdrwtool).
Download from http://sourceforge.net/projects/linux-udf/
+ Note: pktsetup needs the pktcdvd procfs interface!

- Grab a new CD-RW disc and format it (assuming CD-RW is hdc, substitute
as appropriate):
@@ -90,8 +92,274 @@ Notes
to create an ext2 filesystem on the disc.


+
+Using the pktcdvd sysfs interface
+---------------------------------
+
+The pktcdvd module has a sysfs interface and can be controlled
+by the tool "pktcdvd" (see attachment) that uses sysfs.
+
+"pktcdvd" works similar to "pktsetup", e.g.:
+
+ # pktcdvd -a dev_name /dev/hdc
+ # mkudffs /dev/pktcdvd/dev_name
+ # mount -t udf -o rw,noatime /dev/pktcdvd/dev_name /dvdram
+ # cp files /dvdram
+ # umount /dvdram
+ # pktcdvd -r dev_name
+
+
+For a description of the sysfs interface look into the file:
+
+ Documentation/ABI/testing/sysfs-block-pktcdvd
+
+
+
+Bio write queue congestion marks
+--------------------------------
+The pktcdvd driver allows now to adjust the behaviour of the
+internal bio write queue.
+This can be done with the two write_congestion_[on|off] marks.
+The driver does only accept up to write_congestion_on bio
+write request from the i/o block layer, and waits till the
+requests are processed by the mapped block device and
+the queue size is below the write_congestion_off mark.
+In previous versions of pktcdvd, the driver accepted all
+incoming bio write request. This led sometimes to kernel
+out of memory oops (maybe some bugs in the linux kernel ;)
+CAUTION: use this options only if you know what you do!
+The default settings for the congestion marks should be ok
+for everyone.
+
+
Links
-----

See http://fy.chalmers.se/~appro/linux/DVD+RW/ for more information
about DVD writing.
+
+
+
+---[snip]-------------------------------------------------
+#!/usr/bin/bash
+###########################################################################
+# #
+# Linux pktcdvd Module Control Script #
+# #
+# Copyright (C) 2006 Thomas Maier <[email protected]> #
+# #
+# May be copied or modified under the terms of the GNU General Public #
+# License. #
+# #
+# 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; #
+# #
+# 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. #
+###########################################################################
+
+
+PKTCDVD_DIR="/sys/block/pktcdvd"
+PKTDEV_DIR="/dev/pktcdvd"
+
+usage()
+{
+ echo "pktcdvd control script Version 1.0.0" >&2
+ echo "Copyright (C) 2006 Thomas Maier <[email protected]>" >&2
+ echo "usage:" >&2
+ echo " # add new packet device for block device" >&2
+ echo " pktcdvd -a pktname blkdev" >&2
+ echo " # remove a packet device" >&2
+ echo " pktcdvd -r pktname|pktmajor:pktminor" >&2
+ echo " # show packet device info" >&2
+ echo " pktcdvd -i pktname|pktmajor:pktminor" >&2
+ echo " # set write congestion marks for packet device" >&2
+ echo " pktcdvd -c pktname|pktmajor:pktminor on [off]" >&2
+ echo " # print write congestion marks for packet device" >&2
+ echo " pktcdvd -C pktname|pktmajor:pktminor" >&2
+ echo " # print device mapping" >&2
+ echo " pktcdvd -m" >&2
+ echo " # set number of concurrent packets for new devices" >&2
+ echo " pktcdvd -p packetbuffers" >&2
+ echo " # print number of concurrent packets" >&2
+ echo " pktcdvd -P" >&2
+}
+
+exiterr()
+{
+ [ -n "$1" ] && echo "error: $1" >&2
+ exit 1
+}
+
+usage_error()
+{
+ usage
+ exiterr
+}
+
+# device file to device id (major:minor)
+dev_to_id()
+{
+ if [[ "$1" == *:* ]] ; then
+ echo "$1"
+ return 0
+ elif [ -b "$1" ] ; then
+ /bin/ls -l "$1" \
+ | awk '{print $5 ":" $6}' | tr -d ','
+ return 0
+ fi
+ return 1
+}
+
+# device file, device id or pktcdvd device name
+# to packet device id (major:minor)
+to_pktdev_id()
+{
+ typeset name="$1"
+ typeset devid=""
+
+ if [ -d "$PKTCDVD_DIR/$name" ] ; then
+ devid="$(cat "$PKTCDVD_DIR/$name/dev")"
+ elif [ -b "$PKTDEV_DIR/$name" ] ; then
+ devid="$(dev_to_id "$PKTDEV_DIR/$name")"
+ else
+ devid="$(dev_to_id "$name")"
+ fi
+ [ -z "$devid" ] && exiterr "can not get device id for $name"
+ echo "$devid"
+}
+
+to_pkt_name()
+{
+ typeset pktdev="$1"
+ typeset pktdevid
+ pktdevid="$(to_pktdev_id "$pktdev")" || exit 1
+ typeset pktname="$(awk '$2=="'"$pktdevid"'" {print $1}' "$PKTCDVD_DIR/device_map" )"
+ [ -z "$pktname" ] && exiterr "device $pktdev is not an active pktcdvd device"
+ echo "$pktname"
+}
+
+to_pkt_sdir()
+{
+ typeset name
+ name="$(to_pkt_name "$1")" || exit 1
+ echo "$PKTCDVD_DIR/$name/packet"
+}
+
+check_root()
+{
+ [ ! -w "$PKTCDVD_DIR/add" ] && exiterr "permission denied"
+}
+
+
+if [[ $# != 0 && ! -d "$PKTCDVD_DIR" ]] ; then
+ exiterr "pktcdvd module not loaded or no sysfs available"
+fi
+
+case "$1" in
+
+-a)
+ (( "$#" != 3 )) && usage_error
+ pktname="$2"
+ blkdev="$3"
+ blkdevid="$(dev_to_id "$blkdev")" || exiterr "not a block device: $blkdev"
+ pktdev="$PKTDEV_DIR/$pktname"
+
+ m="$(grep " $blkdevid\$" "$PKTCDVD_DIR/device_map")"
+ [ -n "$m" ] && exiterr "device $blkdev already mapped to: $m"
+
+ check_root
+ echo "$blkdevid" >"$PKTCDVD_DIR/add" || exiterr "unable to add new pktcdvd device"
+ pktdevid="$(awk '$3=="'"$blkdevid"'" {print $2}' "$PKTCDVD_DIR/device_map" )"
+ [ -z "$pktdevid" ] && exiterr "can not add new pktcdvd device"
+ pktsysn="$(awk '$3=="'"$blkdevid"'" {print $1}' "$PKTCDVD_DIR/device_map" )"
+
+ rm -f "$pktdev" "$PKTDEV_DIR/$pktsysn"
+ mkdir -p "$PKTDEV_DIR" || exiterr
+ major="$(echo "$pktdevid" | cut -f 1 -d ':')"
+ minor="$(echo "$pktdevid" | cut -f 2 -d ':')"
+ mknod "$PKTDEV_DIR/$pktsysn" b "$major" "$minor" \
+ || exiterr "failed to make device node "$PKTDEV_DIR/$pktsysn" for $major:$minor"
+ chmod 644 "$PKTDEV_DIR/$pktsysn"
+ # create hard link, so dev_to_id() can resolve it!
+ [[ "$pktsysn" != "$pktname" ]] && ln -f "$PKTDEV_DIR/$pktsysn" "$pktdev"
+
+ echo "ok: $pktdev ($pktsysn [$pktdevid] -> $blkdev [$blkdevid])"
+ ;;
+
+-r)
+ (( "$#" != 2 )) && usage_error
+ pktdev="$2"
+ pktdevid="$(to_pktdev_id "$pktdev")" || exit 1
+
+ pktsysn="$(awk '$2=="'"$pktdevid"'" {print $1}' "$PKTCDVD_DIR/device_map" )"
+ [ -z "$pktsysn" ] && exiterr "device $pktdev is not an active pktcdvd device"
+
+ check_root
+ echo "$pktdevid" >"$PKTCDVD_DIR/remove" \
+ || exiterr "unable to remove pktcdvd device $pktdev ($pktdevid)"
+ fgrep " $pktdevid " "$PKTCDVD_DIR/device_map" >/dev/null \
+ && exiterr "unable to remove pktcdvd device $pktdev ($pktdevid) or device busy"
+
+ # use the inode number of $pktsysn to remove all names of the device
+ inode="$(/bin/ls -i1 "$PKTDEV_DIR/$pktsysn" | awk '{print $1}')"
+ find "$PKTDEV_DIR" -follow -inum "$inode" -exec rm {} \;
+ ;;
+
+-i)
+ (( "$#" != 2 )) && usage_error
+ pktdev="$2"
+ pktsdir="$(to_pkt_sdir "$pktdev")" || exit 1
+ cat "$pktsdir/info"
+ exit "$?"
+ ;;
+
+-c)
+ (( "$#" < 3 )) && usage_error
+ pktdev="$2"
+ valon="$3"
+ valoff="$4"
+ sdir="$(to_pkt_sdir "$pktdev")" || exit 1
+ check_root
+ echo "$valon" >"$sdir/write_congestion_on"
+ [ -n "$valoff" ] && echo "$valoff" >"$sdir/write_congestion_off"
+ echo "off=$(cat "$sdir/write_congestion_off") on=$(cat "$sdir/write_congestion_on")"
+ ;;
+
+-C)
+ (( "$#" != 2 )) && usage_error
+ pktdev="$2"
+ sdir="$(to_pkt_sdir "$pktdev")" || exit 1
+ echo "off=$(cat "$sdir/write_congestion_off") on=$(cat "$sdir/write_congestion_on")"
+ ;;
+
+-m)
+ (( "$#" != 1 )) && usage_error
+ cat "$PKTCDVD_DIR/device_map"
+ exit "$?"
+ ;;
+
+-p)
+ (( "$#" != 2 )) && usage_error
+ check_root
+ echo "$2" >"$PKTCDVD_DIR/packet_buffers"
+ ret="$?"
+ cat "$PKTCDVD_DIR/packet_buffers"
+ exit "$ret"
+ ;;
+
+-P)
+ (( "$#" != 1 )) && usage_error
+ cat "$PKTCDVD_DIR/packet_buffers"
+ exit "$?"
+ ;;
+
+*)
+ usage_error
+ ;;
+esac
+
+exit 0
diff -urpN linux-2.6.18-rc6/block/genhd.c pktcdvd-patch-2.6.18-rc6/block/genhd.c
--- linux-2.6.18-rc6/block/genhd.c 2006-09-04 21:24:46.000000000 +0200
+++ pktcdvd-patch-2.6.18-rc6/block/genhd.c 2006-09-05 20:29:41.000000000 +0200
@@ -510,6 +510,7 @@ static struct kset_uevent_ops block_ueve
};

decl_subsys(block, &ktype_block, &block_uevent_ops);
+EXPORT_SYMBOL(block_subsys);

/*
* aggregate disk stat collector. Uses the same stats that the sysfs
diff -urpN linux-2.6.18-rc6/drivers/block/Kconfig pktcdvd-patch-2.6.18-rc6/drivers/block/Kconfig
--- linux-2.6.18-rc6/drivers/block/Kconfig 2006-09-04 21:25:04.000000000 +0200
+++ pktcdvd-patch-2.6.18-rc6/drivers/block/Kconfig 2006-09-05 20:29:41.000000000 +0200
@@ -428,17 +428,22 @@ config CDROM_PKTCDVD
tristate "Packet writing on CD/DVD media"
depends on !UML
help
- If you have a CDROM drive that supports packet writing, say Y to
- include preliminary support. It should work with any MMC/Mt Fuji
- compliant ATAPI or SCSI drive, which is just about any newer CD
- writer.
+ If you have a CDROM/DVD drive that supports packet writing, say
+ Y to include preliminary support. It should work with any
+ MMC/Mt Fuji compliant ATAPI or SCSI drive, which is just about
+ any newer DVD/CD writer.

- Currently only writing to CD-RW, DVD-RW and DVD+RW discs is possible.
+ Currently only writing to CD-RW, DVD-RW, DVD+RW and DVDRAM discs
+ is possible.
DVD-RW disks must be in restricted overwrite mode.

To compile this driver as a module, choose M here: the
module will be called pktcdvd.

+ For further information see the file
+
+ Documentation/cdrom/packet-writing.txt
+
config CDROM_PKTCDVD_BUFFERS
int "Free buffers for data gathering"
depends on CDROM_PKTCDVD
@@ -458,6 +463,21 @@ config CDROM_PKTCDVD_WCACHE
this option is dangerous unless the CD-RW media is known good, as we
don't do deferred write error handling yet.

+config CDROM_PKTCDVD_PROCINTF
+ bool "Enable procfs interface"
+ depends on CDROM_PKTCDVD
+ default y
+ help
+ Enable the proc filesystem interface for pktcdvd.
+ The files can be found as:
+
+ /proc/driver/pktcdvd/pktcdvd<idx>
+
+ Also a misc device is added with a dynamic minor device id.
+ See the entry in /proc/misc for the minor number.
+ The misc pktcdvd driver supports ioctl calls, needed
+ by the pktsetup tool found in udftools package.
+
source "drivers/s390/block/Kconfig"

config ATA_OVER_ETH
diff -urpN linux-2.6.18-rc6/drivers/block/pktcdvd.c pktcdvd-patch-2.6.18-rc6/drivers/block/pktcdvd.c
--- linux-2.6.18-rc6/drivers/block/pktcdvd.c 2006-09-04 21:24:59.000000000 +0200
+++ pktcdvd-patch-2.6.18-rc6/drivers/block/pktcdvd.c 2006-09-05 20:29:41.000000000 +0200
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2000 Jens Axboe <[email protected]>
* Copyright (C) 2001-2004 Peter Osterlund <[email protected]>
+ * Copyright (C) 2006 Thomas Maier <[email protected]>
*
* May be copied or modified under the terms of the GNU General Public
* License. See linux/COPYING for more information.
@@ -41,8 +42,15 @@
* gathering to convert the unaligned writes to aligned writes and then feeds
* them to the packet I/O scheduler.
*
+ *
+ * 08/19/2006 Thomas Maier <[email protected]>
+ * - added support for bio write queue congestion marks
+ * 08/26/2006 Thomas Maier <[email protected]>
+ * - added sysfs support
+ *
*************************************************************************/

+#include <linux/config.h>
#include <linux/pktcdvd.h>
#include <linux/module.h>
#include <linux/types.h>
@@ -56,12 +64,14 @@
#include <linux/miscdevice.h>
#include <linux/suspend.h>
#include <linux/mutex.h>
+#include <linux/sched.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/scsi.h>

#include <asm/uaccess.h>

+
#if PACKET_DEBUG
#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
#else
@@ -78,12 +88,531 @@

#define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))

+static int pktdev_major = 0; /* default: dynamic major number */
+static int write_congestion_on = PKT_WRITE_CONGESTION_ON;
+static int write_congestion_off = PKT_WRITE_CONGESTION_OFF;
+static int packet_buffers = PKT_BUFFERS_DEFAULT;
+
static struct pktcdvd_device *pkt_devs[MAX_WRITERS];
-static struct proc_dir_entry *pkt_proc;
-static int pkt_major;
static struct mutex ctl_mutex; /* Serialize open/close/setup/teardown */
static mempool_t *psd_pool;

+static struct kobject *kobj_blk_pktcdvd = NULL;
+
+
+/********************************************************
+ * utils
+ *******************************************************/
+
+static struct pktcdvd_device *pkt_find_dev_from_minor(int dev_minor)
+{
+ if (dev_minor >= MAX_WRITERS)
+ return NULL;
+ return pkt_devs[dev_minor];
+}
+
+static struct pktcdvd_device *pkt_find_dev(dev_t devid, int* pidx)
+{
+ int idx;
+ for (idx = 0; idx < MAX_WRITERS; idx++) {
+ struct pktcdvd_device *pd = pkt_devs[idx];
+ if (pd && (pd->pkt_dev == devid)) {
+ if (pidx)
+ *pidx = idx;
+ return pd;
+ }
+ }
+ if (pidx)
+ *pidx = 0;
+ return NULL;
+}
+
+static struct pktcdvd_device *pkt_find_dev_bdev(dev_t bdevid, int* pidx)
+{
+ int idx;
+ for (idx = 0; idx < MAX_WRITERS; idx++) {
+ struct pktcdvd_device *pd = pkt_devs[idx];
+ if (pd && pd->bdev && (pd->bdev->bd_dev == bdevid)) {
+ if (pidx)
+ *pidx = idx;
+ return pd;
+ }
+ }
+ if (pidx)
+ *pidx = 0;
+ return NULL;
+}
+
+static void init_congestion(int* lo, int* hi)
+{
+ if (*hi > 0) {
+ *hi = max(*hi, PKT_WRITE_CONGESTION_MIN);
+ *hi = min(*hi, PKT_WRITE_CONGESTION_MAX);
+ if (*lo <= 0)
+ *lo = *hi - PKT_WRITE_CONGESTION_THRESHOLD;
+ else {
+ *lo = min(*lo, *hi - PKT_WRITE_CONGESTION_THRESHOLD);
+ *lo = max(*lo, PKT_WRITE_CONGESTION_MIN/4);
+ }
+ } else {
+ *hi = -1;
+ *lo = -1;
+ }
+}
+
+static void init_packet_buffers(int *n)
+{
+ if (*n < 1)
+ *n = 1;
+ else if (*n > 128)
+ *n = 128;
+}
+
+static void pkt_count_states(struct pktcdvd_device *pd, int *states)
+{
+ struct packet_data *pkt;
+ int i;
+
+ for (i = 0; i < PACKET_NUM_STATES; i++)
+ states[i] = 0;
+
+ spin_lock(&pd->cdrw.active_list_lock);
+ list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+ states[pkt->state]++;
+ }
+ spin_unlock(&pd->cdrw.active_list_lock);
+}
+
+static int pkt_print_info(struct pktcdvd_device *pd, char *buf, int blen)
+{
+ char *msg;
+ char bdev_buf[BDEVNAME_SIZE];
+ int states[PACKET_NUM_STATES];
+ int n = 0;
+
+#define PRINT n += scnprintf
+#define BC buf+n, blen-n
+ PRINT(BC, "Writer %s mapped to %s:\n", pd->name,
+ bdevname(pd->bdev, bdev_buf));
+
+ PRINT(BC, "\nSettings:\n");
+ PRINT(BC, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
+
+ if (pd->settings.write_type == 0)
+ msg = "Packet";
+ else
+ msg = "Unknown";
+ PRINT(BC, "\twrite type:\t\t%s\n", msg);
+
+ PRINT(BC, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable");
+ PRINT(BC, "\tlink loss:\t\t%d\n", pd->settings.link_loss);
+
+ PRINT(BC, "\ttrack mode:\t\t%d\n", pd->settings.track_mode);
+
+ if (pd->settings.block_mode == PACKET_BLOCK_MODE1)
+ msg = "Mode 1";
+ else if (pd->settings.block_mode == PACKET_BLOCK_MODE2)
+ msg = "Mode 2";
+ else
+ msg = "Unknown";
+ PRINT(BC, "\tblock mode:\t\t%s\n", msg);
+ PRINT(BC, "\tconcurrent packets:\t%d\n", packet_buffers);
+
+ PRINT(BC, "\nStatistics:\n");
+ PRINT(BC, "\tpackets started:\t%lu\n", pd->stats.pkt_started);
+ PRINT(BC, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended);
+ PRINT(BC, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1);
+ PRINT(BC, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1);
+ PRINT(BC, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
+
+ PRINT(BC, "\nMisc:\n");
+ PRINT(BC, "\treference count:\t%d\n", pd->refcnt);
+ PRINT(BC, "\tflags:\t\t\t0x%lx\n", pd->flags);
+ PRINT(BC, "\tread speed:\t\t%ukB/s\n", pd->read_speed);
+ PRINT(BC, "\twrite speed:\t\t%ukB/s\n", pd->write_speed);
+ PRINT(BC, "\tstart offset:\t\t%lu\n", pd->offset);
+ PRINT(BC, "\tmode page offset:\t%u\n", pd->mode_offset);
+
+ PRINT(BC, "\nQueue state:\n");
+ PRINT(BC, "\tbios queued:\t\t%d\n", pd->bio_queue_size);
+ PRINT(BC, "\tbios pending:\t\t%d\n", atomic_read(&pd->cdrw.pending_bios));
+ PRINT(BC, "\tcurrent sector:\t\t0x%llx\n",
+ (unsigned long long)pd->current_sector);
+
+ pkt_count_states(pd, states);
+ PRINT(BC, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
+ states[0], states[1], states[2], states[3], states[4], states[5]);
+
+ PRINT(BC, "\twrite congestion marks:\toff=%d on=%d\n",
+ pd->write_congestion_off,
+ pd->write_congestion_on);
+#undef PRINT
+#undef BC
+ buf[blen-1] = 0;
+ return n;
+}
+
+
+
+/**********************************************************
+ *
+ * sysfs interface for pktcdvd
+ * by (C) 2006 Thomas Maier <[email protected]>
+ *
+ **********************************************************/
+
+#define to_pktdev(_k) container_of(_k, struct pktcdvd_device, kobj)
+
+#define DEF_ATTR(_obj,_name,_mode) \
+ static struct attribute _obj = { \
+ .name = _name, .owner = THIS_MODULE, .mode = _mode }
+
+/* forward declaration */
+static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev);
+static int pkt_remove_dev(dev_t pkt_dev);
+
+/**********************************************************
+ /sys/block/pktcdvd/pktcdvd?/packet/
+ mapped_to (symlink)
+ statistic
+ reset_statistic
+ write_congestion_off
+ write_congestion_on
+ **********************************************************/
+
+DEF_ATTR(kobj_pkt_attr_stat, "statistic", 0444);
+DEF_ATTR(kobj_pkt_attr_reset_stat, "reset_statistic", 0200);
+DEF_ATTR(kobj_pkt_attr_info, "info", 0444);
+DEF_ATTR(kobj_pkt_attr_wqueue_size, "write_queue_size", 0444);
+DEF_ATTR(kobj_pkt_attr_wcong_off, "write_congestion_off", 0644);
+DEF_ATTR(kobj_pkt_attr_wcong_on, "write_congestion_on", 0644);
+
+/* forward declaration of kobject release function */
+static void kobj_pkt_release(struct kobject *kobj);
+
+
+static ssize_t kobj_pkt_show(struct kobject *kobj,
+ struct attribute *attr, char *data)
+{
+ struct pktcdvd_device *pd;
+ int n = 0;
+ int v;
+
+ if (!kobj || !attr || !data)
+ return 0;
+
+ data[0] = 0;
+ pd = to_pktdev(kobj);
+
+ if (strcmp(attr->name, "statistic") == 0) {
+ n = sprintf(data, "%lu %lu %lu %lu %lu\n",
+ pd->stats.pkt_started,
+ pd->stats.pkt_ended,
+ pd->stats.secs_w >> 1,
+ pd->stats.secs_rg >> 1,
+ pd->stats.secs_r >> 1);
+
+ } else if (strcmp(attr->name, "info") == 0) {
+ n = pkt_print_info(pd, data, PAGE_SIZE-32);
+
+ } else if (strcmp(attr->name, "write_queue_size") == 0) {
+ spin_lock(&pd->lock);
+ v = pd->bio_queue_size;
+ spin_unlock(&pd->lock);
+ n = sprintf(data, "%d\n", v);
+
+ } else if (strcmp(attr->name, "write_congestion_off") == 0) {
+ spin_lock(&pd->lock);
+ v = pd->write_congestion_off;
+ spin_unlock(&pd->lock);
+ n = sprintf(data, "%d\n", v);
+
+ } else if (strcmp(attr->name, "write_congestion_on") == 0) {
+ spin_lock(&pd->lock);
+ v = pd->write_congestion_on;
+ spin_unlock(&pd->lock);
+ n = sprintf(data, "%d\n", v);
+ }
+ return n;
+}
+
+static ssize_t kobj_pkt_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *data, size_t len)
+{
+ int val;
+ struct pktcdvd_device *pd;
+ char dbuf[64];
+ int dlen = len;
+
+ if (!kobj || !attr || !data || len < 0)
+ goto out;
+ /* make sure we have a zero terminated string */
+ if (dlen >= sizeof(dbuf))
+ dlen = sizeof(dbuf)-1;
+ memcpy(dbuf, data, dlen);
+ dbuf[dlen] = 0;
+
+ pd = to_pktdev(kobj);
+
+ if (strcmp(attr->name, "reset_statistic") == 0 && dlen > 0) {
+ pd->stats.pkt_started = 0;
+ pd->stats.pkt_ended = 0;
+ pd->stats.secs_w = 0;
+ pd->stats.secs_rg = 0;
+ pd->stats.secs_r = 0;
+
+ } else if (strcmp(attr->name, "write_congestion_off") == 0
+ && sscanf(dbuf, "%d", &val) == 1) {
+ spin_lock(&pd->lock);
+ pd->write_congestion_off = val;
+ init_congestion(&pd->write_congestion_off, &pd->write_congestion_on);
+ spin_unlock(&pd->lock);
+ } else if (strcmp(attr->name, "write_congestion_on") == 0
+ && sscanf(dbuf, "%d", &val) == 1) {
+ spin_lock(&pd->lock);
+ pd->write_congestion_on = val;
+ init_congestion(&pd->write_congestion_off, &pd->write_congestion_on);
+ spin_unlock(&pd->lock);
+ }
+out:
+ return len;
+}
+
+static struct attribute *kobj_pkt_attrs[] = {
+ &kobj_pkt_attr_stat,
+ &kobj_pkt_attr_reset_stat,
+ &kobj_pkt_attr_info,
+ &kobj_pkt_attr_wqueue_size,
+ &kobj_pkt_attr_wcong_off,
+ &kobj_pkt_attr_wcong_on,
+ NULL
+};
+static struct sysfs_ops kobj_pkt_ops = {
+ .show = kobj_pkt_show,
+ .store = kobj_pkt_store
+};
+static struct kobj_type kobj_pkt_type = {
+ .release = kobj_pkt_release,
+ .sysfs_ops = &kobj_pkt_ops,
+ .default_attrs = kobj_pkt_attrs
+};
+
+
+
+/********************************************************************
+ /sys/block/pktcdvd/
+ add map block device
+ remove unmap packet dev
+ device_map show mappings
+ packet_buffers number of packet buffers per dev
+ *******************************************************************/
+
+DEF_ATTR(kobj_blk_pktcdvd_attr_add, "add", 0200);
+DEF_ATTR(kobj_blk_pktcdvd_attr_rm, "remove", 0200);
+DEF_ATTR(kobj_blk_pktcdvd_attr_map, "device_map", 0444);
+DEF_ATTR(kobj_blk_pktcdvd_attr_bufs, "packet_buffers", 0644);
+
+static void kobj_blk_pktcdvd_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static ssize_t kobj_blk_pktcdvd_show(struct kobject *kobj,
+ struct attribute *attr, char *data)
+{
+ int n = 0;
+
+ /* available size of data is PAGE_SIZE */
+ if (!kobj || !attr || !data)
+ return 0;
+
+ data[0] = 0;
+
+ if (strcmp(attr->name, "device_map") == 0) {
+ int idx;
+ mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
+ for (idx = 0; idx < MAX_WRITERS; idx++) {
+ struct pktcdvd_device *pd = pkt_devs[idx];
+ if (!pd)
+ continue;
+ n += sprintf(data+n, "%s %u:%u %u:%u\n",
+ pd->name,
+ MAJOR(pd->pkt_dev), MINOR(pd->pkt_dev),
+ MAJOR(pd->bdev->bd_dev),
+ MINOR(pd->bdev->bd_dev));
+ }
+ mutex_unlock(&ctl_mutex);
+
+ } else if (strcmp(attr->name, "packet_buffers") == 0) {
+ n = sprintf(data, "%d\n", packet_buffers);
+ }
+ return n;
+}
+
+static ssize_t kobj_blk_pktcdvd_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *data, size_t len)
+{
+ unsigned int pkt_major, pkt_minor;
+ unsigned int blk_major, blk_minor;
+ int val;
+ char dbuf[64];
+ int dlen = len;
+
+ if (!kobj || !attr || !data || len < 0)
+ goto out;
+ /* make sure we have a zero terminated string */
+ if (dlen >= sizeof(dbuf))
+ dlen = sizeof(dbuf)-1;
+ memcpy(dbuf, data, dlen);
+ dbuf[dlen] = 0;
+
+ if (strcmp(attr->name, "add") == 0
+ && sscanf(dbuf, "%u:%u", &blk_major, &blk_minor) == 2) {
+ pkt_setup_dev(MKDEV(blk_major, blk_minor), NULL);
+
+ } else if (strcmp(attr->name, "remove") == 0
+ && sscanf(dbuf, "%u:%u", &pkt_major, &pkt_minor) == 2) {
+ pkt_remove_dev(MKDEV(pkt_major, pkt_minor));
+
+ } else if (strcmp(attr->name, "packet_buffers") == 0
+ && sscanf(dbuf, "%d", &val) == 1) {
+ mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
+ init_packet_buffers(&val);
+ packet_buffers = val;
+ mutex_unlock(&ctl_mutex);
+ }
+out:
+ return len;
+}
+
+static struct attribute *kobj_blk_pktcdvd_attrs[] = {
+ &kobj_blk_pktcdvd_attr_add,
+ &kobj_blk_pktcdvd_attr_rm,
+ &kobj_blk_pktcdvd_attr_map,
+ &kobj_blk_pktcdvd_attr_bufs,
+ NULL
+};
+static struct sysfs_ops kobj_blk_pktcdvd_ops = {
+ .show = kobj_blk_pktcdvd_show,
+ .store = kobj_blk_pktcdvd_store
+};
+static struct kobj_type kobj_blk_pktcdvd_type = {
+ .release = kobj_blk_pktcdvd_release,
+ .sysfs_ops = &kobj_blk_pktcdvd_ops,
+ .default_attrs = kobj_blk_pktcdvd_attrs
+};
+
+
+#if PKT_USE_OLD_PROC
+/********************************************************
+ *
+ * (old) procfs interface
+ *
+ *******************************************************/
+
+static struct proc_dir_entry *pkt_proc;
+
+
+/* file operations for /proc/driver/pktcdvd/.. files */
+
+static int pkt_seq_show(struct seq_file *m, void *p)
+{
+ struct pktcdvd_device *pd = m->private;
+ char buf[2048];
+
+ pkt_print_info(pd, buf, sizeof(buf));
+ seq_printf(m, "%s", buf);
+ return 0;
+}
+
+static int pkt_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pkt_seq_show, PDE(inode)->data);
+}
+
+static struct file_operations pkt_proc_fops = {
+ .open = pkt_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release
+};
+
+
+/* ioctl call for pktcdvd misc device */
+
+static void pkt_get_status(struct pkt_ctrl_command *ctrl_cmd)
+{
+ struct pktcdvd_device *pd;
+
+ mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
+
+ pd = pkt_find_dev_from_minor(ctrl_cmd->dev_index);
+ if (pd) {
+ ctrl_cmd->dev = new_encode_dev(pd->bdev->bd_dev);
+ ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev);
+ } else {
+ ctrl_cmd->dev = 0;
+ ctrl_cmd->pkt_dev = 0;
+ }
+ ctrl_cmd->num_devices = MAX_WRITERS;
+
+ mutex_unlock(&ctl_mutex);
+}
+
+static int pkt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct pkt_ctrl_command ctrl_cmd;
+ int ret = 0;
+ dev_t pkt_dev;
+
+ if (cmd != PACKET_CTRL_CMD)
+ return -ENOTTY;
+
+ if (copy_from_user(&ctrl_cmd, argp, sizeof(struct pkt_ctrl_command)))
+ return -EFAULT;
+
+ switch (ctrl_cmd.command) {
+ case PKT_CTRL_CMD_SETUP:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ ret = pkt_setup_dev(new_decode_dev(ctrl_cmd.dev), &pkt_dev);
+ ctrl_cmd.pkt_dev = new_encode_dev(pkt_dev);
+ break;
+ case PKT_CTRL_CMD_TEARDOWN:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ ret = pkt_remove_dev(new_decode_dev(ctrl_cmd.pkt_dev));
+ break;
+ case PKT_CTRL_CMD_STATUS:
+ pkt_get_status(&ctrl_cmd);
+ break;
+ default:
+ return -ENOTTY;
+ }
+
+ if (copy_to_user(argp, &ctrl_cmd, sizeof(struct pkt_ctrl_command)))
+ return -EFAULT;
+ return ret;
+}
+
+static struct file_operations pkt_ctl_fops = {
+ .ioctl = pkt_ctl_ioctl,
+ .owner = THIS_MODULE,
+};
+static struct miscdevice pkt_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "pktcdvd",
+ .fops = &pkt_ctl_fops
+};
+
+#endif /* PKT_USE_OLD_PROC */
+
+
+/****************************************************************/
+

static void pkt_bio_finished(struct pktcdvd_device *pd)
{
@@ -891,6 +1420,7 @@ static int pkt_handle_queue(struct pktcd
sector_t zone = 0; /* Suppress gcc warning */
struct pkt_rb_node *node, *first_node;
struct rb_node *n;
+ int wakeup;

VPRINTK("handle_queue\n");

@@ -963,7 +1493,14 @@ try_next_bio:
pkt->write_size += bio->bi_size / CD_FRAMESIZE;
spin_unlock(&pkt->lock);
}
+ /* check write congestion marks, and if bio_queue_size is
+ below, wake up any waiters */
+ wakeup = (pd->write_congestion_on > 0
+ && pd->bio_queue_size <= pd->write_congestion_off
+ && waitqueue_active(&pd->write_congestion_wqueue));
spin_unlock(&pd->lock);
+ if (wakeup)
+ wake_up(&pd->write_congestion_wqueue);

pkt->sleep_time = max(PACKET_WAIT_TIME, 1);
pkt_set_state(pkt, PACKET_WAITING_STATE);
@@ -1165,21 +1702,6 @@ static void pkt_handle_packets(struct pk
spin_unlock(&pd->cdrw.active_list_lock);
}

-static void pkt_count_states(struct pktcdvd_device *pd, int *states)
-{
- struct packet_data *pkt;
- int i;
-
- for (i = 0; i < PACKET_NUM_STATES; i++)
- states[i] = 0;
-
- spin_lock(&pd->cdrw.active_list_lock);
- list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
- states[pkt->state]++;
- }
- spin_unlock(&pd->cdrw.active_list_lock);
-}
-
/*
* kcdrwd is woken up when writes have been queued for one of our
* registered devices
@@ -1907,26 +2429,226 @@ static int pkt_open_write(struct pktcdvd
return 0;
}

-/*
- * called at open time.
- */
-static int pkt_open_dev(struct pktcdvd_device *pd, int write)
+
+static int pkt_end_io_read_cloned(struct bio *bio, unsigned int bytes_done, int err)
{
- int ret;
- long lba;
- request_queue_t *q;
+ struct packet_stacked_data *psd = bio->bi_private;
+ struct pktcdvd_device *pd = psd->pd;

- /*
- * We need to re-open the cdrom device without O_NONBLOCK to be able
- * to read/write from/to it. It is already opened in O_NONBLOCK mode
- * so bdget() can't fail.
- */
- bdget(pd->bdev->bd_dev);
- if ((ret = blkdev_get(pd->bdev, FMODE_READ, O_RDONLY)))
- goto out;
+ if (bio->bi_size)
+ return 1;

- if ((ret = bd_claim(pd->bdev, pd)))
- goto out_putdev;
+ bio_put(bio);
+ bio_endio(psd->bio, psd->bio->bi_size, err);
+ mempool_free(psd, psd_pool);
+ pkt_bio_finished(pd);
+ return 0;
+}
+
+/*
+ * callback from block layer.
+ * process the bio request from the block layer.
+ */
+static int pkt_make_request(request_queue_t *q, struct bio *bio)
+{
+ struct pktcdvd_device *pd;
+ char b[BDEVNAME_SIZE];
+ sector_t zone;
+ struct packet_data *pkt;
+ int was_empty, blocked_bio;
+ struct pkt_rb_node *node;
+
+ pd = q->queuedata;
+ if (!pd) {
+ printk("pktcdvd: %s incorrect request queue\n", bdevname(bio->bi_bdev, b));
+ goto end_io;
+ }
+
+ /*
+ * Clone READ bios so we can have our own bi_end_io callback.
+ */
+ if (bio_data_dir(bio) == READ) {
+ struct bio *cloned_bio = bio_clone(bio, GFP_NOIO);
+ struct packet_stacked_data *psd = mempool_alloc(psd_pool, GFP_NOIO);
+
+ psd->pd = pd;
+ psd->bio = bio;
+ cloned_bio->bi_bdev = pd->bdev;
+ cloned_bio->bi_private = psd;
+ cloned_bio->bi_end_io = pkt_end_io_read_cloned;
+ pd->stats.secs_r += bio->bi_size >> 9;
+ pkt_queue_bio(pd, cloned_bio);
+ return 0;
+ }
+
+ if (!test_bit(PACKET_WRITABLE, &pd->flags)) {
+ printk("pktcdvd: WRITE for ro device %s (%llu)\n",
+ pd->name, (unsigned long long)bio->bi_sector);
+ goto end_io;
+ }
+
+ if (!bio->bi_size || (bio->bi_size % CD_FRAMESIZE)) {
+ printk("pktcdvd: wrong bio size\n");
+ goto end_io;
+ }
+
+ blk_queue_bounce(q, &bio);
+
+ zone = ZONE(bio->bi_sector, pd);
+ VPRINTK("pkt_make_request: start = %6llx stop = %6llx\n",
+ (unsigned long long)bio->bi_sector,
+ (unsigned long long)(bio->bi_sector + bio_sectors(bio)));
+
+ /* Check if we have to split the bio */
+ {
+ struct bio_pair *bp;
+ sector_t last_zone;
+ int first_sectors;
+
+ last_zone = ZONE(bio->bi_sector + bio_sectors(bio) - 1, pd);
+ if (last_zone != zone) {
+ BUG_ON(last_zone != zone + pd->settings.size);
+ first_sectors = last_zone - bio->bi_sector;
+ bp = bio_split(bio, bio_split_pool, first_sectors);
+ BUG_ON(!bp);
+ pkt_make_request(q, &bp->bio1);
+ pkt_make_request(q, &bp->bio2);
+ bio_pair_release(bp);
+ return 0;
+ }
+ }
+
+ /*
+ * If we find a matching packet in state WAITING or READ_WAIT, we can
+ * just append this bio to that packet.
+ */
+ spin_lock(&pd->cdrw.active_list_lock);
+ blocked_bio = 0;
+ list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+ if (pkt->sector == zone) {
+ spin_lock(&pkt->lock);
+ if ((pkt->state == PACKET_WAITING_STATE) ||
+ (pkt->state == PACKET_READ_WAIT_STATE)) {
+ pkt_add_list_last(bio, &pkt->orig_bios,
+ &pkt->orig_bios_tail);
+ pkt->write_size += bio->bi_size / CD_FRAMESIZE;
+ if ((pkt->write_size >= pkt->frames) &&
+ (pkt->state == PACKET_WAITING_STATE)) {
+ atomic_inc(&pkt->run_sm);
+ wake_up(&pd->wqueue);
+ }
+ spin_unlock(&pkt->lock);
+ spin_unlock(&pd->cdrw.active_list_lock);
+ return 0;
+ } else {
+ blocked_bio = 1;
+ }
+ spin_unlock(&pkt->lock);
+ }
+ }
+ spin_unlock(&pd->cdrw.active_list_lock);
+
+ /*
+ * Test if there is enough room left in the bio work queue
+ * (queue size >= congestion on mark).
+ * If not, wait till the work queue size is below the congestion off mark.
+ * This is similar to the get_request_wait() call made in the block
+ * layer function __make_request() used for normal block i/o request
+ * handling.
+ */
+ spin_lock(&pd->lock);
+ if (pd->write_congestion_on > 0
+ && pd->bio_queue_size >= pd->write_congestion_on) {
+ DEFINE_WAIT(wait);
+ /* wait till number of bio requests is low enough */
+ do {
+ spin_unlock(&pd->lock);
+ prepare_to_wait_exclusive(&pd->write_congestion_wqueue,
+ &wait, TASK_UNINTERRUPTIBLE);
+ io_schedule();
+ /* if we are here, bio_queue_size should be below
+ congestion_off, but be sure and do a test */
+ spin_lock(&pd->lock);
+ } while(pd->bio_queue_size > pd->write_congestion_off);
+ finish_wait(&pd->write_congestion_wqueue, &wait);
+ }
+ spin_unlock(&pd->lock);
+
+ /*
+ * No matching packet found. Store the bio in the work queue.
+ */
+ node = mempool_alloc(pd->rb_pool, GFP_NOIO);
+ node->bio = bio;
+ spin_lock(&pd->lock);
+ BUG_ON(pd->bio_queue_size < 0);
+ was_empty = (pd->bio_queue_size == 0);
+ pkt_rbtree_insert(pd, node);
+ spin_unlock(&pd->lock);
+
+ /*
+ * Wake up the worker thread.
+ */
+ atomic_set(&pd->scan_queue, 1);
+ if (was_empty) {
+ /* This wake_up is required for correct operation */
+ wake_up(&pd->wqueue);
+ } else if (!list_empty(&pd->cdrw.pkt_free_list) && !blocked_bio) {
+ /*
+ * This wake up is not required for correct operation,
+ * but improves performance in some cases.
+ */
+ wake_up(&pd->wqueue);
+ }
+ return 0;
+end_io:
+ bio_io_error(bio, bio->bi_size);
+ return 0;
+}
+
+/*
+ * callback from block layer
+ */
+static int pkt_merge_bvec(request_queue_t *q, struct bio *bio, struct bio_vec *bvec)
+{
+ struct pktcdvd_device *pd = q->queuedata;
+ sector_t zone = ZONE(bio->bi_sector, pd);
+ int used = ((bio->bi_sector - zone) << 9) + bio->bi_size;
+ int remaining = (pd->settings.size << 9) - used;
+ int remaining2;
+
+ /*
+ * A bio <= PAGE_SIZE must be allowed. If it crosses a packet
+ * boundary, pkt_make_request() will split the bio.
+ */
+ remaining2 = PAGE_SIZE - bio->bi_size;
+ remaining = max(remaining, remaining2);
+
+ BUG_ON(remaining < 0);
+ return remaining;
+}
+
+
+
+/*
+ * called at open time.
+ */
+static int pkt_open_dev(struct pktcdvd_device *pd, int write)
+{
+ int ret;
+ long lba;
+ request_queue_t *q;
+
+ /*
+ * We need to re-open the cdrom device without O_NONBLOCK to be able
+ * to read/write from/to it. It is already opened in O_NONBLOCK mode
+ * so bdget() can't fail.
+ */
+ bdget(pd->bdev->bd_dev);
+ if ((ret = blkdev_get(pd->bdev, FMODE_READ, O_RDONLY)))
+ goto out;
+
+ if ((ret = bd_claim(pd->bdev, pd)))
+ goto out_putdev;

if ((ret = pkt_get_last_written(pd, &lba))) {
printk("pktcdvd: pkt_get_last_written failed\n");
@@ -1958,7 +2680,7 @@ static int pkt_open_dev(struct pktcdvd_d
goto out_unclaim;

if (write) {
- if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) {
+ if (!pkt_grow_pktlist(pd, packet_buffers)) {
printk("pktcdvd: not enough memory for buffers\n");
ret = -ENOMEM;
goto out_unclaim;
@@ -1994,12 +2716,13 @@ static void pkt_release_dev(struct pktcd
pkt_shrink_pktlist(pd);
}

-static struct pktcdvd_device *pkt_find_dev_from_minor(int dev_minor)
-{
- if (dev_minor >= MAX_WRITERS)
- return NULL;
- return pkt_devs[dev_minor];
-}
+
+
+/******************************************************************
+ *
+ * pktcdvd block device operations
+ *
+ *****************************************************************/

static int pkt_open(struct inode *inode, struct file *file)
{
@@ -2061,174 +2784,74 @@ static int pkt_close(struct inode *inode
return ret;
}

-
-static int pkt_end_io_read_cloned(struct bio *bio, unsigned int bytes_done, int err)
-{
- struct packet_stacked_data *psd = bio->bi_private;
- struct pktcdvd_device *pd = psd->pd;
-
- if (bio->bi_size)
- return 1;
-
- bio_put(bio);
- bio_endio(psd->bio, psd->bio->bi_size, err);
- mempool_free(psd, psd_pool);
- pkt_bio_finished(pd);
- return 0;
-}
-
-static int pkt_make_request(request_queue_t *q, struct bio *bio)
+static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
- struct pktcdvd_device *pd;
- char b[BDEVNAME_SIZE];
- sector_t zone;
- struct packet_data *pkt;
- int was_empty, blocked_bio;
- struct pkt_rb_node *node;
-
- pd = q->queuedata;
- if (!pd) {
- printk("pktcdvd: %s incorrect request queue\n", bdevname(bio->bi_bdev, b));
- goto end_io;
- }
-
- /*
- * Clone READ bios so we can have our own bi_end_io callback.
- */
- if (bio_data_dir(bio) == READ) {
- struct bio *cloned_bio = bio_clone(bio, GFP_NOIO);
- struct packet_stacked_data *psd = mempool_alloc(psd_pool, GFP_NOIO);
-
- psd->pd = pd;
- psd->bio = bio;
- cloned_bio->bi_bdev = pd->bdev;
- cloned_bio->bi_private = psd;
- cloned_bio->bi_end_io = pkt_end_io_read_cloned;
- pd->stats.secs_r += bio->bi_size >> 9;
- pkt_queue_bio(pd, cloned_bio);
- return 0;
- }
-
- if (!test_bit(PACKET_WRITABLE, &pd->flags)) {
- printk("pktcdvd: WRITE for ro device %s (%llu)\n",
- pd->name, (unsigned long long)bio->bi_sector);
- goto end_io;
- }
-
- if (!bio->bi_size || (bio->bi_size % CD_FRAMESIZE)) {
- printk("pktcdvd: wrong bio size\n");
- goto end_io;
- }
-
- blk_queue_bounce(q, &bio);
-
- zone = ZONE(bio->bi_sector, pd);
- VPRINTK("pkt_make_request: start = %6llx stop = %6llx\n",
- (unsigned long long)bio->bi_sector,
- (unsigned long long)(bio->bi_sector + bio_sectors(bio)));
-
- /* Check if we have to split the bio */
- {
- struct bio_pair *bp;
- sector_t last_zone;
- int first_sectors;
-
- last_zone = ZONE(bio->bi_sector + bio_sectors(bio) - 1, pd);
- if (last_zone != zone) {
- BUG_ON(last_zone != zone + pd->settings.size);
- first_sectors = last_zone - bio->bi_sector;
- bp = bio_split(bio, bio_split_pool, first_sectors);
- BUG_ON(!bp);
- pkt_make_request(q, &bp->bio1);
- pkt_make_request(q, &bp->bio2);
- bio_pair_release(bp);
- return 0;
- }
- }
+ struct pktcdvd_device *pd = inode->i_bdev->bd_disk->private_data;

- /*
- * If we find a matching packet in state WAITING or READ_WAIT, we can
- * just append this bio to that packet.
- */
- spin_lock(&pd->cdrw.active_list_lock);
- blocked_bio = 0;
- list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
- if (pkt->sector == zone) {
- spin_lock(&pkt->lock);
- if ((pkt->state == PACKET_WAITING_STATE) ||
- (pkt->state == PACKET_READ_WAIT_STATE)) {
- pkt_add_list_last(bio, &pkt->orig_bios,
- &pkt->orig_bios_tail);
- pkt->write_size += bio->bi_size / CD_FRAMESIZE;
- if ((pkt->write_size >= pkt->frames) &&
- (pkt->state == PACKET_WAITING_STATE)) {
- atomic_inc(&pkt->run_sm);
- wake_up(&pd->wqueue);
- }
- spin_unlock(&pkt->lock);
- spin_unlock(&pd->cdrw.active_list_lock);
- return 0;
- } else {
- blocked_bio = 1;
- }
- spin_unlock(&pkt->lock);
- }
- }
- spin_unlock(&pd->cdrw.active_list_lock);
+ VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));

+ switch (cmd) {
/*
- * No matching packet found. Store the bio in the work queue.
+ * forward selected CDROM ioctls to CD-ROM, for UDF
*/
- node = mempool_alloc(pd->rb_pool, GFP_NOIO);
- node->bio = bio;
- spin_lock(&pd->lock);
- BUG_ON(pd->bio_queue_size < 0);
- was_empty = (pd->bio_queue_size == 0);
- pkt_rbtree_insert(pd, node);
- spin_unlock(&pd->lock);
+ case CDROMMULTISESSION:
+ case CDROMREADTOCENTRY:
+ case CDROM_LAST_WRITTEN:
+ case CDROM_SEND_PACKET:
+ case SCSI_IOCTL_SEND_COMMAND:
+ return blkdev_ioctl(pd->bdev->bd_inode, file, cmd, arg);

- /*
- * Wake up the worker thread.
- */
- atomic_set(&pd->scan_queue, 1);
- if (was_empty) {
- /* This wake_up is required for correct operation */
- wake_up(&pd->wqueue);
- } else if (!list_empty(&pd->cdrw.pkt_free_list) && !blocked_bio) {
+ case CDROMEJECT:
/*
- * This wake up is not required for correct operation,
- * but improves performance in some cases.
+ * The door gets locked when the device is opened, so we
+ * have to unlock it or else the eject command fails.
*/
- wake_up(&pd->wqueue);
+ if (pd->refcnt == 1)
+ pkt_lock_door(pd, 0);
+ return blkdev_ioctl(pd->bdev->bd_inode, file, cmd, arg);
+
+ default:
+ VPRINTK("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd);
+ return -ENOTTY;
}
- return 0;
-end_io:
- bio_io_error(bio, bio->bi_size);
+
return 0;
}

+static int pkt_media_changed(struct gendisk *disk)
+{
+ struct pktcdvd_device *pd = disk->private_data;
+ struct gendisk *attached_disk;

+ if (!pd)
+ return 0;
+ if (!pd->bdev)
+ return 0;
+ attached_disk = pd->bdev->bd_disk;
+ if (!attached_disk)
+ return 0;
+ return attached_disk->fops->media_changed(attached_disk);
+}

-static int pkt_merge_bvec(request_queue_t *q, struct bio *bio, struct bio_vec *bvec)
-{
- struct pktcdvd_device *pd = q->queuedata;
- sector_t zone = ZONE(bio->bi_sector, pd);
- int used = ((bio->bi_sector - zone) << 9) + bio->bi_size;
- int remaining = (pd->settings.size << 9) - used;
- int remaining2;
+static struct block_device_operations pktcdvd_ops = {
+ .owner = THIS_MODULE,
+ .open = pkt_open,
+ .release = pkt_close,
+ .ioctl = pkt_ioctl,
+ .media_changed = pkt_media_changed,
+};

- /*
- * A bio <= PAGE_SIZE must be allowed. If it crosses a packet
- * boundary, pkt_make_request() will split the bio.
- */
- remaining2 = PAGE_SIZE - bio->bi_size;
- remaining = max(remaining, remaining2);

- BUG_ON(remaining < 0);
- return remaining;
-}
+/***********************************************************************
+ *
+ * packet device setup and removal
+ *
+ **********************************************************************/

-static void pkt_init_queue(struct pktcdvd_device *pd)
+/*
+ * helper for pkt_new_dev()
+ */
+ static void pkt_init_queue(struct pktcdvd_device *pd)
{
request_queue_t *q = pd->disk->queue;

@@ -2238,84 +2861,14 @@ static void pkt_init_queue(struct pktcdv
blk_queue_merge_bvec(q, pkt_merge_bvec);
q->queuedata = pd;
}
-
-static int pkt_seq_show(struct seq_file *m, void *p)
-{
- struct pktcdvd_device *pd = m->private;
- char *msg;
- char bdev_buf[BDEVNAME_SIZE];
- int states[PACKET_NUM_STATES];
-
- seq_printf(m, "Writer %s mapped to %s:\n", pd->name,
- bdevname(pd->bdev, bdev_buf));
-
- seq_printf(m, "\nSettings:\n");
- seq_printf(m, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
-
- if (pd->settings.write_type == 0)
- msg = "Packet";
- else
- msg = "Unknown";
- seq_printf(m, "\twrite type:\t\t%s\n", msg);
-
- seq_printf(m, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable");
- seq_printf(m, "\tlink loss:\t\t%d\n", pd->settings.link_loss);
-
- seq_printf(m, "\ttrack mode:\t\t%d\n", pd->settings.track_mode);
-
- if (pd->settings.block_mode == PACKET_BLOCK_MODE1)
- msg = "Mode 1";
- else if (pd->settings.block_mode == PACKET_BLOCK_MODE2)
- msg = "Mode 2";
- else
- msg = "Unknown";
- seq_printf(m, "\tblock mode:\t\t%s\n", msg);
-
- seq_printf(m, "\nStatistics:\n");
- seq_printf(m, "\tpackets started:\t%lu\n", pd->stats.pkt_started);
- seq_printf(m, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended);
- seq_printf(m, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1);
- seq_printf(m, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1);
- seq_printf(m, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
-
- seq_printf(m, "\nMisc:\n");
- seq_printf(m, "\treference count:\t%d\n", pd->refcnt);
- seq_printf(m, "\tflags:\t\t\t0x%lx\n", pd->flags);
- seq_printf(m, "\tread speed:\t\t%ukB/s\n", pd->read_speed);
- seq_printf(m, "\twrite speed:\t\t%ukB/s\n", pd->write_speed);
- seq_printf(m, "\tstart offset:\t\t%lu\n", pd->offset);
- seq_printf(m, "\tmode page offset:\t%u\n", pd->mode_offset);
-
- seq_printf(m, "\nQueue state:\n");
- seq_printf(m, "\tbios queued:\t\t%d\n", pd->bio_queue_size);
- seq_printf(m, "\tbios pending:\t\t%d\n", atomic_read(&pd->cdrw.pending_bios));
- seq_printf(m, "\tcurrent sector:\t\t0x%llx\n", (unsigned long long)pd->current_sector);
-
- pkt_count_states(pd, states);
- seq_printf(m, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
- states[0], states[1], states[2], states[3], states[4], states[5]);
-
- return 0;
-}
-
-static int pkt_seq_open(struct inode *inode, struct file *file)
-{
- return single_open(file, pkt_seq_show, PDE(inode)->data);
-}
-
-static struct file_operations pkt_proc_fops = {
- .open = pkt_seq_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release
-};
-
+/*
+ * helper for pkt_setup_dev()
+ */
static int pkt_new_dev(struct pktcdvd_device *pd, dev_t dev)
{
int i;
int ret = 0;
char b[BDEVNAME_SIZE];
- struct proc_dir_entry *proc;
struct block_device *bdev;

if (pd->pkt_dev == dev) {
@@ -2342,10 +2895,19 @@ static int pkt_new_dev(struct pktcdvd_de
ret = blkdev_get(bdev, FMODE_READ, O_RDONLY | O_NONBLOCK);
if (ret)
return ret;
-
+
/* This is safe, since we have a reference from open(). */
__module_get(THIS_MODULE);

+ /* the block device must have a queue ! else many of the
+ pktcdvd code breaks. */
+ if (!bdev_get_queue(bdev)) {
+ printk("pktcdvd: block device %s has no request queue, aborting\n",
+ bdevname(bdev, b));
+ ret = -ENXIO;
+ goto out_mem;
+ }
+
pd->bdev = bdev;
set_blocksize(bdev, CD_FRAMESIZE);

@@ -2359,11 +2921,16 @@ static int pkt_new_dev(struct pktcdvd_de
goto out_mem;
}

- proc = create_proc_entry(pd->name, 0, pkt_proc);
- if (proc) {
- proc->data = pd;
- proc->proc_fops = &pkt_proc_fops;
+#if PKT_USE_OLD_PROC
+ {
+ struct proc_dir_entry *proc = create_proc_entry(pd->name, 0, pkt_proc);
+ if (proc) {
+ proc->data = pd;
+ proc->proc_fops = &pkt_proc_fops;
+ }
}
+#endif
+
DPRINTK("pktcdvd: writer %s mapped to %s\n", pd->name, bdevname(bdev, b));
return 0;

@@ -2374,96 +2941,36 @@ out_mem:
return ret;
}

-static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
-{
- struct pktcdvd_device *pd = inode->i_bdev->bd_disk->private_data;
-
- VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
-
- switch (cmd) {
- /*
- * forward selected CDROM ioctls to CD-ROM, for UDF
- */
- case CDROMMULTISESSION:
- case CDROMREADTOCENTRY:
- case CDROM_LAST_WRITTEN:
- case CDROM_SEND_PACKET:
- case SCSI_IOCTL_SEND_COMMAND:
- return blkdev_ioctl(pd->bdev->bd_inode, file, cmd, arg);
-
- case CDROMEJECT:
- /*
- * The door gets locked when the device is opened, so we
- * have to unlock it or else the eject command fails.
- */
- if (pd->refcnt == 1)
- pkt_lock_door(pd, 0);
- return blkdev_ioctl(pd->bdev->bd_inode, file, cmd, arg);
-
- default:
- VPRINTK("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd);
- return -ENOTTY;
- }
-
- return 0;
-}
-
-static int pkt_media_changed(struct gendisk *disk)
-{
- struct pktcdvd_device *pd = disk->private_data;
- struct gendisk *attached_disk;
-
- if (!pd)
- return 0;
- if (!pd->bdev)
- return 0;
- attached_disk = pd->bdev->bd_disk;
- if (!attached_disk)
- return 0;
- return attached_disk->fops->media_changed(attached_disk);
-}
-
-static struct block_device_operations pktcdvd_ops = {
- .owner = THIS_MODULE,
- .open = pkt_open,
- .release = pkt_close,
- .ioctl = pkt_ioctl,
- .media_changed = pkt_media_changed,
-};
-
/*
* Set up mapping from pktcdvd device to CD-ROM device.
*/
-static int pkt_setup_dev(struct pkt_ctrl_command *ctrl_cmd)
+static int pkt_setup_dev(dev_t dev, dev_t* pkt_dev)
{
int idx;
int ret = -ENOMEM;
struct pktcdvd_device *pd;
struct gendisk *disk;
- dev_t dev = new_decode_dev(ctrl_cmd->dev);
+
+ mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);

for (idx = 0; idx < MAX_WRITERS; idx++)
if (!pkt_devs[idx])
break;
if (idx == MAX_WRITERS) {
printk("pktcdvd: max %d writers supported\n", MAX_WRITERS);
- return -EBUSY;
+ ret = -EBUSY;
+ goto out_mutex;
}

pd = kzalloc(sizeof(struct pktcdvd_device), GFP_KERNEL);
if (!pd)
- return ret;
-
+ goto out_mutex;
+
pd->rb_pool = mempool_create_kmalloc_pool(PKT_RB_POOL_SIZE,
sizeof(struct pkt_rb_node));
if (!pd->rb_pool)
goto out_mem;
-
- disk = alloc_disk(1);
- if (!disk)
- goto out_mem;
- pd->disk = disk;
-
+
INIT_LIST_HEAD(&pd->cdrw.pkt_free_list);
INIT_LIST_HEAD(&pd->cdrw.pkt_active_list);
spin_lock_init(&pd->cdrw.active_list_lock);
@@ -2474,7 +2981,15 @@ static int pkt_setup_dev(struct pkt_ctrl
init_waitqueue_head(&pd->wqueue);
pd->bio_queue = RB_ROOT;

- disk->major = pkt_major;
+ init_waitqueue_head(&pd->write_congestion_wqueue);
+ pd->write_congestion_on = write_congestion_on;
+ pd->write_congestion_off = write_congestion_off;
+
+ disk = alloc_disk(1);
+ if (!disk)
+ goto out_mem;
+ pd->disk = disk;
+ disk->major = pktdev_major;
disk->first_minor = idx;
disk->fops = &pktcdvd_ops;
disk->flags = GENHD_FL_REMOVABLE;
@@ -2489,11 +3004,41 @@ static int pkt_setup_dev(struct pkt_ctrl
if (ret)
goto out_new_dev;

+ /* move disk kobject to a child of the pktcdvd node
+ in /sys/block, so that the disk is now available as
+
+ /sys/block/pktcdvd/<diskname>
+ */
+ disk->kobj.parent = kobj_blk_pktcdvd;
+
add_disk(disk);
+
+ /*
+ * setup kernel object after add_disk(), because
+ * we are a child of the disk's kobject!
+ */
+ strlcpy(pd->kobj.name, "packet", KOBJ_NAME_LEN);
+ pd->kobj.parent = &disk->kobj;
+ pd->kobj.ktype = &kobj_pkt_type;
+ if (kobject_register(&pd->kobj) != 0) {
+ printk("pktcdvd: failed to register block pktcdvd "
+ "kernel object\n");
+ goto out_disk;
+ }
+ if (pd->bdev->bd_disk)
+ sysfs_create_link(&pd->kobj, &pd->bdev->bd_disk->kobj,
+ "mapped_to");
+
pkt_devs[idx] = pd;
- ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev);
+ if (pkt_dev)
+ *pkt_dev = pd->pkt_dev;
+
+ mutex_unlock(&ctl_mutex);
return 0;

+out_disk:
+ blkdev_put(pd->bdev);
+ del_gendisk(pd->disk);
out_new_dev:
blk_cleanup_queue(disk->queue);
out_mem2:
@@ -2502,149 +3047,144 @@ out_mem:
if (pd->rb_pool)
mempool_destroy(pd->rb_pool);
kfree(pd);
+out_mutex:
+ mutex_unlock(&ctl_mutex);
+ printk("pktcdvd: setup of pktcdvd device failed\n");
return ret;
}

/*
* Tear down mapping from pktcdvd device to CD-ROM device.
*/
-static int pkt_remove_dev(struct pkt_ctrl_command *ctrl_cmd)
+static int pkt_remove_dev(dev_t pkt_dev)
{
struct pktcdvd_device *pd;
int idx;
- dev_t pkt_dev = new_decode_dev(ctrl_cmd->pkt_dev);
+ int ret = 0;
+
+ mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);

- for (idx = 0; idx < MAX_WRITERS; idx++) {
- pd = pkt_devs[idx];
- if (pd && (pd->pkt_dev == pkt_dev))
- break;
- }
- if (idx == MAX_WRITERS) {
- DPRINTK("pktcdvd: dev not setup\n");
- return -ENXIO;
+ pd = pkt_find_dev(pkt_dev, &idx);
+ if (!pd) {
+ /* maybe pkt_dev is the mapped block device id */
+ pd = pkt_find_dev_bdev(pkt_dev, &idx);
+ if (!pd) {
+ DPRINTK("pktcdvd: dev not setup\n");
+ ret = -ENXIO;
+ goto out;
+ }
}

- if (pd->refcnt > 0)
- return -EBUSY;
-
+ if (pd->refcnt > 0) {
+ ret = -EBUSY;
+ goto out;
+ }
if (!IS_ERR(pd->cdrw.thread))
kthread_stop(pd->cdrw.thread);
+
+ pkt_devs[idx] = NULL;
+
+ /* calls kobj_pkt_release() if last user of the kobject */
+ sysfs_remove_link(&pd->kobj, "mapped_to");
+ kobject_unregister(&pd->kobj);
+out:
+ mutex_unlock(&ctl_mutex);
+ return ret;
+}

+static void kobj_pkt_release(struct kobject *kobj)
+{
+ struct pktcdvd_device *pd = to_pktdev(kobj);
+
blkdev_put(pd->bdev);

+#if PKT_USE_OLD_PROC
remove_proc_entry(pd->name, pkt_proc);
+#endif
DPRINTK("pktcdvd: writer %s unmapped\n", pd->name);

del_gendisk(pd->disk);
blk_cleanup_queue(pd->disk->queue);
put_disk(pd->disk);

- pkt_devs[idx] = NULL;
mempool_destroy(pd->rb_pool);
kfree(pd);

/* This is safe: open() is still holding a reference. */
module_put(THIS_MODULE);
- return 0;
-}
-
-static void pkt_get_status(struct pkt_ctrl_command *ctrl_cmd)
-{
- struct pktcdvd_device *pd = pkt_find_dev_from_minor(ctrl_cmd->dev_index);
- if (pd) {
- ctrl_cmd->dev = new_encode_dev(pd->bdev->bd_dev);
- ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev);
- } else {
- ctrl_cmd->dev = 0;
- ctrl_cmd->pkt_dev = 0;
- }
- ctrl_cmd->num_devices = MAX_WRITERS;
-}
-
-static int pkt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
-{
- void __user *argp = (void __user *)arg;
- struct pkt_ctrl_command ctrl_cmd;
- int ret = 0;
-
- if (cmd != PACKET_CTRL_CMD)
- return -ENOTTY;
-
- if (copy_from_user(&ctrl_cmd, argp, sizeof(struct pkt_ctrl_command)))
- return -EFAULT;
-
- switch (ctrl_cmd.command) {
- case PKT_CTRL_CMD_SETUP:
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
- mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
- ret = pkt_setup_dev(&ctrl_cmd);
- mutex_unlock(&ctl_mutex);
- break;
- case PKT_CTRL_CMD_TEARDOWN:
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
- mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
- ret = pkt_remove_dev(&ctrl_cmd);
- mutex_unlock(&ctl_mutex);
- break;
- case PKT_CTRL_CMD_STATUS:
- mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING);
- pkt_get_status(&ctrl_cmd);
- mutex_unlock(&ctl_mutex);
- break;
- default:
- return -ENOTTY;
- }
-
- if (copy_to_user(argp, &ctrl_cmd, sizeof(struct pkt_ctrl_command)))
- return -EFAULT;
- return ret;
}


-static struct file_operations pkt_ctl_fops = {
- .ioctl = pkt_ctl_ioctl,
- .owner = THIS_MODULE,
-};

-static struct miscdevice pkt_misc = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "pktcdvd",
- .fops = &pkt_ctl_fops
-};
+/********************************************************
+ *
+ * module init and exit
+ *
+ *******************************************************/

static int __init pkt_init(void)
{
int ret;

+ /* check module parameters */
+ init_congestion(&write_congestion_off, &write_congestion_on);
+ init_packet_buffers(&packet_buffers);
+
+ mutex_init(&ctl_mutex);
+
psd_pool = mempool_create_kmalloc_pool(PSD_POOL_SIZE,
sizeof(struct packet_stacked_data));
if (!psd_pool)
return -ENOMEM;

- ret = register_blkdev(pkt_major, "pktcdvd");
+ ret = register_blkdev(pktdev_major, "pktcdvd");
if (ret < 0) {
printk("pktcdvd: Unable to register block device\n");
goto out2;
}
- if (!pkt_major)
- pkt_major = ret;
+ if (!pktdev_major)
+ pktdev_major = ret;

+ /*
+ * create control files in sysfs
+ * /sys/block/pktcdvd/...
+ */
+ kobj_blk_pktcdvd = kzalloc(sizeof(*kobj_blk_pktcdvd), GFP_KERNEL);
+ if (!kobj_blk_pktcdvd) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ strlcpy(kobj_blk_pktcdvd->name, "pktcdvd", KOBJ_NAME_LEN);
+ kobj_blk_pktcdvd->parent = &block_subsys.kset.kobj;
+ kobj_blk_pktcdvd->ktype = &kobj_blk_pktcdvd_type;
+ ret = kobject_register(kobj_blk_pktcdvd);
+ if (ret) {
+ printk("pktcdvd: failed to register"
+ " pktcdvd kernel object\n");
+ kfree(kobj_blk_pktcdvd);
+ kobj_blk_pktcdvd = NULL;
+ goto out;
+ }
+
+#if PKT_USE_OLD_PROC
ret = misc_register(&pkt_misc);
if (ret) {
printk("pktcdvd: Unable to register misc device\n");
- goto out;
+ goto out_misc;
}
-
- mutex_init(&ctl_mutex);
-
pkt_proc = proc_mkdir("pktcdvd", proc_root_driver);
-
+#endif
return 0;

+
+#if PKT_USE_OLD_PROC
+out_misc:
+ kobject_unregister(kobj_blk_pktcdvd);
+ kobj_blk_pktcdvd = NULL;
+#endif
+
out:
- unregister_blkdev(pkt_major, "pktcdvd");
+ unregister_blkdev(pktdev_major, "pktcdvd");
out2:
mempool_destroy(psd_pool);
return ret;
@@ -2652,15 +3192,31 @@ out2:

static void __exit pkt_exit(void)
{
+ if (kobj_blk_pktcdvd)
+ kobject_unregister(kobj_blk_pktcdvd);
+ kobj_blk_pktcdvd = NULL;
+
+#if PKT_USE_OLD_PROC
remove_proc_entry("pktcdvd", proc_root_driver);
misc_deregister(&pkt_misc);
- unregister_blkdev(pkt_major, "pktcdvd");
+#endif
+ unregister_blkdev(pktdev_major, "pktcdvd");
mempool_destroy(psd_pool);
}

+/***************************************************************
+ * module declaration
+ **************************************************************/
+
MODULE_DESCRIPTION("Packet writing layer for CD/DVD drives");
-MODULE_AUTHOR("Jens Axboe <[email protected]>");
+MODULE_AUTHOR("Jens Axboe <[email protected]>,Peter Osterlund <[email protected]>,Thomas Maier <[email protected]>");
MODULE_LICENSE("GPL");

module_init(pkt_init);
module_exit(pkt_exit);
+
+module_param(pktdev_major, int, 0);
+module_param(write_congestion_on, int, 0);
+module_param(write_congestion_off, int, 0);
+module_param(packet_buffers, int, 0);
+
diff -urpN linux-2.6.18-rc6/include/linux/pktcdvd.h pktcdvd-patch-2.6.18-rc6/include/linux/pktcdvd.h
--- linux-2.6.18-rc6/include/linux/pktcdvd.h 2006-09-04 21:24:36.000000000 +0200
+++ pktcdvd-patch-2.6.18-rc6/include/linux/pktcdvd.h 2006-09-05 20:29:41.000000000 +0200
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2000 Jens Axboe <[email protected]>
* Copyright (C) 2001-2004 Peter Osterlund <[email protected]>
+ * Copyright (C) 2006 Thomas Maier <[email protected]>
*
* May be copied or modified under the terms of the GNU General Public
* License. See linux/COPYING for more information.
@@ -8,12 +9,51 @@
* Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and
* DVD-RW devices.
*
+ *
+ * 08/19/2006 Thomas Maier <[email protected]>
+ * - added support for bio write queue congestion marks
+ * 08/26/2006 Thomas Maier <[email protected]>
+ * - added sysfs support
+ *
*/
#ifndef __PKTCDVD_H
#define __PKTCDVD_H

#include <linux/types.h>

+
+#define PKT_CTRL_CMD_SETUP 0
+#define PKT_CTRL_CMD_TEARDOWN 1
+#define PKT_CTRL_CMD_STATUS 2
+
+struct pkt_ctrl_command {
+ __u32 command; /* in: Setup, teardown, status */
+ __u32 dev_index; /* in/out: Device index */
+ __u32 dev; /* in/out: Device nr for cdrw device */
+ __u32 pkt_dev; /* in/out: Device nr for packet device */
+ __u32 num_devices; /* out: Largest device index + 1 */
+ __u32 padding; /* Not used */
+};
+
+/*
+ * packet ioctls
+ */
+#define PACKET_IOCTL_MAGIC ('X')
+#define PACKET_CTRL_CMD _IOWR(PACKET_IOCTL_MAGIC, 1, struct pkt_ctrl_command)
+
+
+
+
+#ifdef __KERNEL__
+
+#include <linux/config.h>
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/cdrom.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+
+
/*
* 1 for normal debug messages, 2 is very verbose. 0 to turn it off.
*/
@@ -23,25 +63,33 @@

#define PKT_RB_POOL_SIZE 512

-/*
- * How long we should hold a non-full packet before starting data gathering.
- */
-#define PACKET_WAIT_TIME (HZ * 5 / 1000)

-/*
- * use drive write caching -- we need deferred error handling to be
- * able to sucessfully recover with this option (drive will return good
- * status as soon as the cdb is validated).
- */
-#if defined(CONFIG_CDROM_PKTCDVD_WCACHE)
-#define USE_WCACHING 1
+/* use (old) procfs interface? */
+#ifdef CONFIG_CDROM_PKTCDVD_PROCINTF
+#define PKT_USE_OLD_PROC 1
#else
-#define USE_WCACHING 0
+#define PKT_USE_OLD_PROC 0
#endif

+/* use concurrent packets per device */
+#ifdef CONFIG_CDROM_PKTCDVD_BUFFERS
+#define PKT_BUFFERS_DEFAULT CONFIG_CDROM_PKTCDVD_BUFFERS
+#else
+#define PKT_BUFFERS_DEFAULT 8
+#endif
+
+/* default bio write queue congestion marks */
+#define PKT_WRITE_CONGESTION_ON 5000
+#define PKT_WRITE_CONGESTION_OFF 4500
+#define PKT_WRITE_CONGESTION_MAX (1024*1024)
+#define PKT_WRITE_CONGESTION_MIN 100
+#define PKT_WRITE_CONGESTION_THRESHOLD 25
+
+
/*
- * No user-servicable parts beyond this point ->
+ * How long we should hold a non-full packet before starting data gathering.
*/
+#define PACKET_WAIT_TIME (HZ * 5 / 1000)

/*
* device types
@@ -88,29 +136,19 @@

#undef PACKET_USE_LS

-#define PKT_CTRL_CMD_SETUP 0
-#define PKT_CTRL_CMD_TEARDOWN 1
-#define PKT_CTRL_CMD_STATUS 2

-struct pkt_ctrl_command {
- __u32 command; /* in: Setup, teardown, status */
- __u32 dev_index; /* in/out: Device index */
- __u32 dev; /* in/out: Device nr for cdrw device */
- __u32 pkt_dev; /* in/out: Device nr for packet device */
- __u32 num_devices; /* out: Largest device index + 1 */
- __u32 padding; /* Not used */
-};

/*
- * packet ioctls
+ * use drive write caching -- we need deferred error handling to be
+ * able to sucessfully recover with this option (drive will return good
+ * status as soon as the cdb is validated).
*/
-#define PACKET_IOCTL_MAGIC ('X')
-#define PACKET_CTRL_CMD _IOWR(PACKET_IOCTL_MAGIC, 1, struct pkt_ctrl_command)
+#if defined(CONFIG_CDROM_PKTCDVD_WCACHE)
+#define USE_WCACHING 1
+#else
+#define USE_WCACHING 0
+#endif

-#ifdef __KERNEL__
-#include <linux/blkdev.h>
-#include <linux/completion.h>
-#include <linux/cdrom.h>

struct packet_settings
{
@@ -243,6 +281,8 @@ struct packet_stacked_data

struct pktcdvd_device
{
+ struct kobject kobj; /* kernel object for this dev */
+
struct block_device *bdev; /* dev attached */
dev_t pkt_dev; /* our dev */
char name[20];
@@ -271,8 +311,15 @@ struct pktcdvd_device

struct packet_iosched iosched;
struct gendisk *disk;
+
+ wait_queue_head_t write_congestion_wqueue;
+ int write_congestion_off;
+ int write_congestion_on;
};

+extern struct subsystem block_subsys;
+
+
#endif /* __KERNEL__ */

#endif /* __PKTCDVD_H */


2006-09-08 18:53:24

by Phillip Susi

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Thomas Maier wrote:
> Hello
>
> (3th try with fixed inline patch)
>
> This is a patch for the packet writing driver pktcdvd.
> It adds a sysfs interface to the driver and a bio write
> queue "congestion" handling.
>

Why does pktcdvd need to handle congestion? Doesn't it get blocked when
trying to send down bios to the underlying device if it's queue is
congested?


2006-09-08 21:00:51

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

On Fri, Sep 08, 2006 at 07:55:08PM +0200, Thomas Maier wrote:
> +/sys/block/pktcdvd/<pktdevname>/packet/
> + statistic (r) Show device statistic. One line with
> + 5 values in following order:
> + packets-started
> + packets-end
> + written in kB
> + read gather in kB
> + read in kB

Please no. One value per file is the sysfs rule.

> + reset_statistic (w) Write any value to it to reset
> + pktcdvd device statistic values, like
> + bytes read/written.
> +
> + info (r) Lots of user readable driver statistics
> + and infos. Multiple lines!

Again, no. Put it in debugfs if you want multiple lines.

> + write_queue_size (r) Contains the size of the bio write
> + queue.
> +
> + write_congestion_off (rw) If bio write queue size is below
> + this mark, accept new bio requests
> + from the block layer.
> +
> + write_congestion_on (rw) If bio write queue size is higher
> + as this mark, do no longer accept
> + bio write requests from the block
> + layer and wait till the pktcdvd
> + device has processed enough bio's
> + so that bio write queue size is
> + below congestion off mark.
> +
> + mapped_to Symbolic link to mapped block device
> + in the /sys/block tree.

Shouldn't this whole thing be in /sys/class/ instead of /sys/block/ ?

thanks,

greg k-h

2006-09-08 21:40:00

by Phillip Susi

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Greg KH wrote:
> On Fri, Sep 08, 2006 at 07:55:08PM +0200, Thomas Maier wrote:
>> +/sys/block/pktcdvd/<pktdevname>/packet/
>> + statistic (r) Show device statistic. One line with
>> + 5 values in following order:
>> + packets-started
>> + packets-end
>> + written in kB
>> + read gather in kB
>> + read in kB
>
> Please no. One value per file is the sysfs rule.
>

Except in cases like this where you want to read the status of the
device at a given point in time, and you can't do that unless you grab
all the values at once.


2006-09-08 22:01:40

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

On Fri, Sep 08, 2006 at 05:40:11PM -0400, Phillip Susi wrote:
> Greg KH wrote:
> >On Fri, Sep 08, 2006 at 07:55:08PM +0200, Thomas Maier wrote:
> >>+/sys/block/pktcdvd/<pktdevname>/packet/
> >>+ statistic (r) Show device statistic. One line with
> >>+ 5 values in following order:
> >>+ packets-started
> >>+ packets-end
> >>+ written in kB
> >>+ read gather in kB
> >>+ read in kB
> >
> >Please no. One value per file is the sysfs rule.
> >
>
> Except in cases like this where you want to read the status of the
> device at a given point in time, and you can't do that unless you grab
> all the values at once.

Then don't use sysfs for that. And is something like this as critical
to get that kind of information all in one atomic chunk? It seems
merely to be informational.

thanks,

greg k-h

2006-09-09 17:55:40

by Thomas Maier

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Hello,

> Thomas Maier wrote:
>> Hello
>>
>> (3th try with fixed inline patch)
>>
>> This is a patch for the packet writing driver pktcdvd.
>> It adds a sysfs interface to the driver and a bio write
>> queue "congestion" handling.
>>
>
> Why does pktcdvd need to handle congestion?

> Doesn't it get blocked when
> trying to send down bios to the underlying device if it's queue is
> congested?

Yes, that is right.

But the pktcdvd driver has an internal queue for *incoming* bio write request
(over own make_request function), and this queue size was unlimited!
Since the underlying (DVD writer in my case) can not process as much
write packets as fast as new bio write requests are passed to the driver,
the bio write queue size increases and increases and increases ;)
Esp if you are write huge files. In my case, i wrote about 2G on data
(using rsync for backup purpose) onto DVDRAM, with some huge files
(about 300 MB). The bio write queue size raised over 200000 and more
and at the end, the kernel said: ups, no more memory available, i
let something fail.

-Thomas Maier

2006-09-09 18:04:12

by Thomas Maier

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Hello,

>> + write_queue_size (r) Contains the size of the bio write
>> + queue.
...
>> + mapped_to Symbolic link to mapped block device
>> + in the /sys/block tree.
>
> Shouldn't this whole thing be in /sys/class/ instead of /sys/block/ ?

Don't know. I thought, the pktcdvd is a block driver, so put
the control files into /sys/block ..
Is /sys/class better? If yes, where to put it?

> On Fri, Sep 08, 2006 at 05:40:11PM -0400, Phillip Susi wrote:
>> Greg KH wrote:
>> >On Fri, Sep 08, 2006 at 07:55:08PM +0200, Thomas Maier wrote:
>> >>+/sys/block/pktcdvd/<pktdevname>/packet/
>> >>+ statistic (r) Show device statistic. One line with
>> >>+ 5 values in following order:
>> >>+ packets-started
>> >>+ packets-end
>> >>+ written in kB
>> >>+ read gather in kB
>> >>+ read in kB
>> >
>> >Please no. One value per file is the sysfs rule.
>> >
>>
>> Except in cases like this where you want to read the status of the
>> device at a given point in time, and you can't do that unless you grab
>> all the values at once.
>
> Then don't use sysfs for that. And is something like this as critical
> to get that kind of information all in one atomic chunk? It seems
> merely to be informational.

The "statistic" and "info" files are only for information purpose.
Into /proc ? No. (i read somewhere /proc should only contain process
information in future)
In debugfs? Hmm, this files should be infos that users should be able
to read, no debug output.

Is it ok, if i split the "statistic" into 5 files, and put the "info"
into debugfs ?

-Thomas Maier

2006-09-09 21:31:22

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

On Sat, Sep 09, 2006 at 08:11:56PM +0200, Thomas Maier wrote:
> Hello,
>
> >> + write_queue_size (r) Contains the size of the bio write
> >> + queue.
> ...
> >> + mapped_to Symbolic link to mapped block device
> >> + in the /sys/block tree.
> >
> > Shouldn't this whole thing be in /sys/class/ instead of /sys/block/ ?
>
> Don't know. I thought, the pktcdvd is a block driver, so put
> the control files into /sys/block ..
> Is /sys/class better? If yes, where to put it?

Use /sys/class/pktcdvd/ and use struct device instead of struct
class_device, so I don't have to convert the code later :)

> > On Fri, Sep 08, 2006 at 05:40:11PM -0400, Phillip Susi wrote:
> >> Greg KH wrote:
> >> >On Fri, Sep 08, 2006 at 07:55:08PM +0200, Thomas Maier wrote:
> >> >>+/sys/block/pktcdvd/<pktdevname>/packet/
> >> >>+ statistic (r) Show device statistic. One line with
> >> >>+ 5 values in following order:
> >> >>+ packets-started
> >> >>+ packets-end
> >> >>+ written in kB
> >> >>+ read gather in kB
> >> >>+ read in kB
> >> >
> >> >Please no. One value per file is the sysfs rule.
> >> >
> >>
> >> Except in cases like this where you want to read the status of the
> >> device at a given point in time, and you can't do that unless you grab
> >> all the values at once.
> >
> > Then don't use sysfs for that. And is something like this as critical
> > to get that kind of information all in one atomic chunk? It seems
> > merely to be informational.
>
> The "statistic" and "info" files are only for information purpose.

Then please split them up into individual files.

> Into /proc ? No. (i read somewhere /proc should only contain process
> information in future)

Exactly.

> In debugfs? Hmm, this files should be infos that users should be able
> to read, no debug output.

Ok, then multiple files please.

> Is it ok, if i split the "statistic" into 5 files, and put the "info"
> into debugfs ?

Yes, that would be fine.

thanks,

greg k-h

2006-09-09 22:52:46

by Kay Sievers

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

On Sat, 2006-09-09 at 14:30 -0700, Greg KH wrote:
> On Sat, Sep 09, 2006 at 08:11:56PM +0200, Thomas Maier wrote:
> > Hello,
> >
> > >> + write_queue_size (r) Contains the size of the bio write
> > >> + queue.
> > ...
> > >> + mapped_to Symbolic link to mapped block device
> > >> + in the /sys/block tree.
> > >
> > > Shouldn't this whole thing be in /sys/class/ instead of /sys/block/ ?
> >
> > Don't know. I thought, the pktcdvd is a block driver, so put
> > the control files into /sys/block ..
> > Is /sys/class better? If yes, where to put it?
>
> Use /sys/class/pktcdvd/ and use struct device instead of struct
> class_device, so I don't have to convert the code later :)

Is this always device specific, or also driver global information? Is
pktcdvd always on a block device? Maybe you just want them to be a group
of attributes in the block device directory where they belong to, like:
/sys/block/sr0/pktcdvd/info
/sys/block/sr0/pktcdvd/write_queue_size
/sys/block/sr0/pktcdvd/...

Does that make sense? We should avoid messing around with symlinks
pointing to other devices, if not absolutely needed. We should also not
create a new device type, just for adding properties to an existing one,
especially if there is not some kind of "device stacking". The
"mapped_to" link to the parent device looks like a wild hack to me, we
should avoid.

Thanks,
Kay

2006-09-10 19:20:04

by Thomas Maier

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Hello,

Am 09.09.2006, 23:30 Uhr, schrieb Greg KH <[email protected]>:

> Use /sys/class/pktcdvd/ and use struct device instead of struct
> class_device, so I don't have to convert the code later :)

I moved the pktcdvd control files (device add/remove/map)
into /sys/class/pktcdvd/.
I only use "struct class", no need to use struct class_device,
since this /sys/class/pktcdvd represents not a real device.
The real devices are in /sys/block/pktcdvd[0-7] as created
by a "struct gendisk".

I have a little improvement request for the class code:
since the create_class() function has no attributes parameter,
i have to copy+paste the class_create() code into the module,
assign the class attribute array to the struct class and do a
class_register().
Would be nice if class_create() can be passed the class
attribute array...


The file layout is now as follows:

/sys/block/pktcdvd[0-7]/ # device dir created by gendisk
packet/ # pktcdvd subdir
<files> # pktcdvd per device files
mapped_to # symlink to mapped device in /sys/block

/sys/class/pktcdvd/ # class dir for control files
add # add new device mapping, creates new pktcdvd device
remove # ...
device_map
packet_buffers

/debugfs/pktcdvd[0-7]/ # per device debugfs dir entry
info # lot of human readable device infos, previous
# found in /proc/driver/pktcdvd[0-7]



Suggestions/Alteratives:
* should there be pktcdvd[0-7] subdirs in /sys/class/pktcdvd/ ?
With content stored in /sys/block/../packet/ ? Or a symlink to it?

* the files in /sys/class/pktcdvd can be moved to /sys/module/pktcdvd/ ?!
Seems that some modules have control files in their /sys/module directory.

-Thomas Maier

2006-09-10 19:25:28

by Thomas Maier

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Hello,

> Is this always device specific, or also driver global information? Is
> pktcdvd always on a block device? Maybe you just want them to be a group
> of attributes in the block device directory where they belong to, like:
> /sys/block/sr0/pktcdvd/info
> /sys/block/sr0/pktcdvd/write_queue_size
> /sys/block/sr0/pktcdvd/...
>
> Does that make sense? We should avoid messing around with symlinks
> pointing to other devices, if not absolutely needed. We should also not
> create a new device type, just for adding properties to an existing one,
> especially if there is not some kind of "device stacking". The
> "mapped_to" link to the parent device looks like a wild hack to me, we
> should avoid.

The pktcdvd driver creates new block devices using a "struct gendisk"
that creates the /sys/block/pktcdvd[0-7]/ entries (alloc_disk() -> add_disk()).

Since the files like write_queue_size are per pktcdvd device and belong to
this device, they should be below the /sys/block/pktcdvd[0-7]/ directory,
not below the e.g. /sys/block/sr0/ .

The pktcdvd driver can only be mapped to block devices, as i know.

-Thomas Maier

2006-09-10 22:27:16

by Kay Sievers

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

On Sun, 2006-09-10 at 21:33 +0200, Thomas Maier wrote:
> Hello,
>
> > Is this always device specific, or also driver global information? Is
> > pktcdvd always on a block device? Maybe you just want them to be a group
> > of attributes in the block device directory where they belong to, like:
> > /sys/block/sr0/pktcdvd/info
> > /sys/block/sr0/pktcdvd/write_queue_size
> > /sys/block/sr0/pktcdvd/...
> >
> > Does that make sense? We should avoid messing around with symlinks
> > pointing to other devices, if not absolutely needed. We should also not
> > create a new device type, just for adding properties to an existing one,
> > especially if there is not some kind of "device stacking". The
> > "mapped_to" link to the parent device looks like a wild hack to me, we
> > should avoid.
>
> The pktcdvd driver creates new block devices using a "struct gendisk"
> that creates the /sys/block/pktcdvd[0-7]/ entries (alloc_disk() -> add_disk()).
>
> Since the files like write_queue_size are per pktcdvd device and belong to
> this device, they should be below the /sys/block/pktcdvd[0-7]/ directory,
> not below the e.g. /sys/block/sr0/ .

So the pktcdvd device have their own device nodes, userspace talks to?

Kay

2006-09-11 19:59:28

by Thomas Maier

[permalink] [raw]
Subject: [PATCH] class.c: added class_create_attrs() function

Hello,

this is a patch based on linux 2.6.18-rc6.
It adds a class_create_attrs() function in
drivers/base/class.c .
This new function works similar to class_create(),
but also allows to create the class attribute files
(if any) during class creation.

-Thomas Maier


diff -urpN linux-2.6.18-rc6/drivers/base/class.c patch/drivers/base/class.c
--- linux-2.6.18-rc6/drivers/base/class.c 2006-09-11 21:45:12.000000000 +0200
+++ patch/drivers/base/class.c 2006-09-11 21:52:06.000000000 +0200
@@ -199,6 +199,18 @@ static int class_device_create_uevent(st
*/
struct class *class_create(struct module *owner, char *name)
{
+ return class_create_attrs(owner, name, NULL);
+}
+/**
+ * class_create_attrs - similar to class_create(), but can also
+ * create class attribute files on class creation.
+ * @owner: pointer to the module that is to "own" this struct class
+ * @name: pointer to a string for the name of this class.
+ * @cls_attrs: pointer to a class_attribute array.
+ */
+struct class *class_create_attrs(struct module *owner, char *name,
+ struct class_attribute *cls_attrs)
+{
struct class *cls;
int retval;

@@ -212,6 +224,7 @@ struct class *class_create(struct module
cls->owner = owner;
cls->class_release = class_create_release;
cls->release = class_device_create_release;
+ cls->class_attrs = cls_attrs;

retval = class_register(cls);
if (retval)
@@ -900,6 +913,7 @@ EXPORT_SYMBOL_GPL(class_remove_file);
EXPORT_SYMBOL_GPL(class_register);
EXPORT_SYMBOL_GPL(class_unregister);
EXPORT_SYMBOL_GPL(class_create);
+EXPORT_SYMBOL_GPL(class_create_attrs);
EXPORT_SYMBOL_GPL(class_destroy);

EXPORT_SYMBOL_GPL(class_device_register);
diff -urpN linux-2.6.18-rc6/include/linux/device.h patch/include/linux/device.h
--- linux-2.6.18-rc6/include/linux/device.h 2006-09-11 21:53:10.000000000 +0200
+++ patch/include/linux/device.h 2006-09-11 21:54:21.000000000 +0200
@@ -272,6 +272,8 @@ extern int class_interface_register(stru
extern void class_interface_unregister(struct class_interface *);

extern struct class *class_create(struct module *owner, char *name);
+extern struct class *class_create_attrs(struct module *owner, char *name,
+ struct class_attribute *cls_attrs);
extern void class_destroy(struct class *cls);
extern struct class_device *class_device_create(struct class *cls,
struct class_device *parent,

2006-09-11 20:04:56

by Thomas Maier

[permalink] [raw]
Subject: Re: [PATCH] pktcdvd: added sysfs interface + bio write queue handling fix

Hello,

> The file layout is now as follows:
>
> /sys/block/pktcdvd[0-7]/ # device dir created by gendisk
> packet/ # pktcdvd subdir
> <files> # pktcdvd per device files
> mapped_to # symlink to mapped device in /sys/block
>
> /sys/class/pktcdvd/ # class dir for control files
> add # add new device mapping, creates new pktcdvd device
> remove # ...
> device_map
> packet_buffers
>
> /debugfs/pktcdvd[0-7]/ # per device debugfs dir entry
> info # lot of human readable device infos, previous
> # found in /proc/driver/pktcdvd[0-7]
>
>

Also added class_device's below of /sys/class/pktcdvd , so that
the file layout is now:

/sys/class/pktcdvd/pktcdvd[0-7]/
dev
uevent


> Use /sys/class/pktcdvd/ and use struct device instead of struct
> class_device, so I don't have to convert the code later

Hmm, sorry. Don't know how to do this. The only thing i found
is the code in class.c, where struct class_device is used to create
these "dev" and "uevent" files...


Any comments?

-Thomas Maier