mirror of https://github.com/red-prig/fpPS4.git
1091 lines
23 KiB
Plaintext
1091 lines
23 KiB
Plaintext
unit kern_conf;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
{$CALLING SysV_ABI_CDecl}
|
|
|
|
interface
|
|
|
|
uses
|
|
mqueue,
|
|
sys_conf,
|
|
kern_param,
|
|
vmount,
|
|
vfile,
|
|
vuio,
|
|
vnode,
|
|
time,
|
|
vm,
|
|
sys_vm_object,
|
|
kern_mtx;
|
|
|
|
implementation
|
|
|
|
uses
|
|
sysutils,
|
|
errno,
|
|
devfs_int,
|
|
vsys_generic,
|
|
systm;
|
|
|
|
function dev2unit(d:p_cdev):Integer; inline;
|
|
begin
|
|
Result:=d^.si_drv0;
|
|
end;
|
|
|
|
function devtoname(dev:p_cdev):PChar; inline;
|
|
begin
|
|
Result:=dev^.si_name;
|
|
end;
|
|
|
|
function cdev2priv(c:Pointer):p_cdev_priv; inline;
|
|
begin
|
|
Result:=c-ptruint(@p_cdev_priv(nil)^.cdp_c);
|
|
end;
|
|
|
|
procedure dev_lock(); public;
|
|
begin
|
|
mtx_lock(devmtx);
|
|
end;
|
|
|
|
procedure dev_unlock(); public;
|
|
begin
|
|
mtx_unlock(devmtx);
|
|
end;
|
|
|
|
procedure dev_ref(dev:p_cdev); public;
|
|
begin
|
|
mtx_lock(devmtx);
|
|
Inc(dev^.si_refcount);
|
|
mtx_unlock(devmtx);
|
|
end;
|
|
|
|
procedure dev_refl(dev:p_cdev); public;
|
|
begin
|
|
mtx_assert(devmtx);
|
|
Inc(dev^.si_refcount);
|
|
end;
|
|
|
|
procedure dev_rel(dev:p_cdev); public;
|
|
var
|
|
flag:Integer;
|
|
begin
|
|
flag:=0;
|
|
|
|
dev_lock();
|
|
Dec(dev^.si_refcount);
|
|
Assert(dev^.si_refcount >= 0,'dev_rel(%s) gave negative count');
|
|
|
|
if (dev^.si_devsw=nil) and
|
|
(dev^.si_refcount=0) then
|
|
begin
|
|
LIST_REMOVE(dev,@dev^.si_list);
|
|
flag:=1;
|
|
end;
|
|
dev_unlock();
|
|
if (flag<>0) then
|
|
begin
|
|
devfs_free(dev);
|
|
end;
|
|
end;
|
|
|
|
function dev_refthread(dev:p_cdev;ref:PInteger):p_cdevsw; public;
|
|
var
|
|
csw:p_cdevsw;
|
|
cdp:p_cdev_priv;
|
|
begin
|
|
if ((dev^.si_flags and SI_ETERNAL)<>0) then
|
|
begin
|
|
ref^:=0;
|
|
Exit(dev^.si_devsw);
|
|
end;
|
|
dev_lock();
|
|
csw:=dev^.si_devsw;
|
|
if (csw<>nil) then
|
|
begin
|
|
cdp:=cdev2priv(dev);
|
|
if ((cdp^.cdp_flags and CDP_SCHED_DTR)=0) then
|
|
Inc(dev^.si_threadcount)
|
|
else
|
|
csw:=nil;
|
|
end;
|
|
dev_unlock();
|
|
ref^:=1;
|
|
Exit(csw);
|
|
end;
|
|
|
|
function devvn_refthread(vp:p_vnode;devp:pp_cdev;ref:PInteger):p_cdevsw; public;
|
|
var
|
|
csw:p_cdevsw;
|
|
cdp:p_cdev_priv;
|
|
dev:p_cdev;
|
|
begin
|
|
if ((vp^.v_vflag and VV_ETERNALDEV)<>0) then
|
|
begin
|
|
dev:=vp^.v_rdev;
|
|
if (dev=nil) then
|
|
begin
|
|
Exit(nil);
|
|
end;
|
|
Assert((dev^.si_flags and SI_ETERNAL)<>0,'Not eternal cdev');
|
|
ref^:=0;
|
|
csw:=dev^.si_devsw;
|
|
Assert(csw<>nil,'Eternal cdev is destroyed');
|
|
devp^:=dev;
|
|
Exit(csw);
|
|
end;
|
|
|
|
csw:=nil;
|
|
dev_lock();
|
|
dev:=vp^.v_rdev;
|
|
if (dev=nil) then
|
|
begin
|
|
dev_unlock();
|
|
Exit(nil);
|
|
end;
|
|
cdp:=cdev2priv(dev);
|
|
if ((cdp^.cdp_flags and CDP_SCHED_DTR)=0) then
|
|
begin
|
|
csw:=dev^.si_devsw;
|
|
if (csw<>nil) then
|
|
begin
|
|
Inc(dev^.si_threadcount);
|
|
end;
|
|
end;
|
|
dev_unlock();
|
|
if (csw<>nil) then
|
|
begin
|
|
devp^:=dev;
|
|
ref^:=1;
|
|
end;
|
|
Exit(csw);
|
|
end;
|
|
|
|
procedure dev_relthread(dev:p_cdev;ref:Integer); public;
|
|
begin
|
|
if (ref=0) then Exit;
|
|
dev_lock();
|
|
Assert(dev^.si_threadcount > 0,'%s threadcount is wrong');
|
|
Dec(dev^.si_threadcount);
|
|
dev_unlock();
|
|
end;
|
|
|
|
{
|
|
* Free all the memory collected while the cdev mutex was
|
|
* locked. Since devmtx is after the system map mutex, free() cannot
|
|
* be called immediately and is postponed until cdev mutex can be
|
|
* dropped.
|
|
}
|
|
procedure dev_unlock_and_free();
|
|
var
|
|
cdp_free:TAILQ_HEAD;
|
|
csw_free:SLIST_HEAD;
|
|
cdp:p_cdev_priv;
|
|
csw:p_cdevsw;
|
|
begin
|
|
mtx_assert(devmtx);
|
|
|
|
{
|
|
* Make the local copy of the list heads while the dev_mtx is
|
|
* held. Free it later.
|
|
}
|
|
TAILQ_INIT(@cdp_free);
|
|
TAILQ_CONCAT(@cdp_free,@cdevp_free_list,@p_cdev_priv(nil)^.cdp_list);
|
|
csw_free:=cdevsw_gt_post_list;
|
|
SLIST_INIT(@cdevsw_gt_post_list);
|
|
|
|
mtx_unlock(devmtx);
|
|
|
|
cdp:=TAILQ_FIRST(@cdp_free);
|
|
while (cdp<>nil) do
|
|
begin
|
|
TAILQ_REMOVE(@cdp_free,cdp,@cdp^.cdp_list);
|
|
devfs_free(@cdp^.cdp_c);
|
|
cdp:=TAILQ_FIRST(@cdp_free);
|
|
end;
|
|
|
|
csw:=SLIST_FIRST(@csw_free);
|
|
while (csw<>nil) do
|
|
begin
|
|
SLIST_REMOVE_HEAD(@csw_free,@p_cdevsw(nil)^.d_postfree_list);
|
|
FreeMem(csw);
|
|
csw:=SLIST_FIRST(@csw_free);
|
|
end;
|
|
end;
|
|
|
|
procedure dev_free_devlocked(cdev:p_cdev);
|
|
var
|
|
cdp:p_cdev_priv;
|
|
begin
|
|
mtx_assert(devmtx);
|
|
cdp:=cdev2priv(cdev);
|
|
Assert((cdp^.cdp_flags and CDP_UNREF_DTR)=0,'destroy_dev() was not called after delist_dev(%p)');
|
|
TAILQ_INSERT_HEAD(@cdevp_free_list,cdp,@cdp^.cdp_list);
|
|
end;
|
|
|
|
procedure cdevsw_free_devlocked(csw:p_cdevsw);
|
|
begin
|
|
mtx_assert(devmtx);
|
|
SLIST_INSERT_HEAD(@cdevsw_gt_post_list,csw,@csw^.d_postfree_list);
|
|
end;
|
|
|
|
procedure dead_strategy(bp:Pointer);
|
|
begin
|
|
//biofinish(bp, nil, ENXIO);
|
|
end;
|
|
|
|
const
|
|
dead_cdevsw:t_cdevsw=(
|
|
d_version :D_VERSION;
|
|
d_flags :0;
|
|
d_name :'dead';
|
|
d_open :d_open_t(@_enxio);
|
|
d_fdopen :nil;
|
|
d_close :d_close_t(@_enxio);
|
|
d_read :d_read_t(@_enxio);
|
|
d_write :d_write_t(@_enxio);
|
|
d_ioctl :d_ioctl_t(@_enxio);
|
|
d_poll :d_poll_t(@_enodev);
|
|
d_mmap :d_mmap_t(@_enodev);
|
|
d_strategy :@dead_strategy;
|
|
d_dump :dumper_t(@_enxio);
|
|
d_kqfilter :d_kqfilter_t(@_enxio);
|
|
d_purge :nil;
|
|
d_mmap_single :d_mmap_single_t(@_enodev);
|
|
d_mmap_single2:d_mmap_single2_t(@_enodev);
|
|
);
|
|
|
|
procedure no_strategy(bp:Pointer);
|
|
begin
|
|
//biofinish(bp, nil, ENODEV);
|
|
end;
|
|
|
|
function no_poll(dev:p_cdev;events:Integer):Integer;
|
|
begin
|
|
Exit(poll_no_poll(events));
|
|
end;
|
|
|
|
function giant_open(dev:p_cdev;oflags,devtype:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_open(dev, oflags, devtype);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
function giant_fdopen(dev:p_cdev;oflags:Integer;fp:p_file):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_fdopen(dev, oflags, fp);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
function giant_close(dev:p_cdev;fflag,devtype:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_close(dev, fflag, devtype);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
procedure giant_strategy(bp:Pointer);
|
|
var
|
|
dsw:p_cdevsw;
|
|
dev:p_cdev;
|
|
ref:Integer;
|
|
begin
|
|
//dev:=bp^.bio_dev;
|
|
//dsw:=dev_refthread(dev, @ref);
|
|
//if (dsw=nil) then
|
|
//begin
|
|
// biofinish(bp, nil, ENXIO);
|
|
// Exit;
|
|
//end;
|
|
//mtx_lock(VFS_Giant);
|
|
//dsw^.d_gianttrick^.d_strategy(bp);
|
|
//mtx_unlock(VFS_Giant);
|
|
//dev_relthread(dev, ref);
|
|
end;
|
|
|
|
function giant_ioctl(dev:p_cdev;cmd:QWORD;data:Pointer;fflag:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_ioctl(dev, cmd, data, fflag);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
function giant_read(dev:p_cdev;uio:p_uio;ioflag:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_read(dev, uio, ioflag);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
function giant_write(dev:p_cdev;uio:p_uio;ioflag:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_write(dev, uio, ioflag);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
function giant_poll(dev:p_cdev;events:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_poll(dev, events);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
function giant_kqfilter(dev:p_cdev;kn:Pointer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_kqfilter(dev, kn);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
Function giant_mmap(dev:p_cdev;offset:vm_ooffset_t;paddr:p_vm_paddr_t;nprot:Integer;memattr:p_vm_memattr_t):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_mmap(dev, offset, paddr, nprot, memattr);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
Function giant_mmap_single(dev:p_cdev;offset:p_vm_ooffset_t;size:vm_size_t;obj:p_vm_object_t;nprot:Integer):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_mmap_single(dev, offset, size, obj, nprot);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
Function giant_mmap_single2(dev:p_cdev;offset:p_vm_ooffset_t;size:vm_size_t;obj:p_vm_object_t;nprot:Integer;maxprotp:p_vm_prot_t;flagsp:PInteger):Integer;
|
|
var
|
|
dsw:p_cdevsw;
|
|
ref,retval:Integer;
|
|
begin
|
|
dsw:=dev_refthread(dev, @ref);
|
|
if (dsw=nil) then
|
|
begin
|
|
Exit(ENXIO);
|
|
end;
|
|
mtx_lock(VFS_Giant);
|
|
retval:=dsw^.d_gianttrick^.d_mmap_single2(dev, offset, size, obj, nprot, maxprotp, flagsp);
|
|
mtx_unlock(VFS_Giant);
|
|
dev_relthread(dev, ref);
|
|
Exit(retval);
|
|
end;
|
|
|
|
procedure notify(dev:p_cdev;ev:PChar;flags:Integer);
|
|
const
|
|
prefix:PChar='cdev=';
|
|
var
|
|
data:PChar;
|
|
namelen:Integer;
|
|
begin
|
|
//if (cold) then Exit;
|
|
namelen:=strlen(dev^.si_name);
|
|
data:=AllocMem(namelen + sizeof(prefix));
|
|
if (data=nil) then Exit;
|
|
Move(prefix^, data^, sizeof(prefix) - 1);
|
|
Move(dev^.si_name^, (data + sizeof(prefix) - 1)^, namelen + 1);
|
|
//devctl_notify_f('DEVFS', 'CDEV', ev, data, mflags);
|
|
FreeMem(data);
|
|
end;
|
|
|
|
procedure notify_create(dev:p_cdev;flags:Integer);
|
|
begin
|
|
notify(dev, 'CREATE', flags);
|
|
end;
|
|
|
|
procedure notify_destroy(dev:p_cdev);
|
|
begin
|
|
notify(dev, 'DESTROY', MAKEDEV_WAITOK);
|
|
end;
|
|
|
|
function newdev(csw:p_cdevsw;_unit:Integer;si:p_cdev):p_cdev;
|
|
var
|
|
si2:p_cdev;
|
|
begin
|
|
mtx_assert(devmtx);
|
|
if ((csw^.d_flags and D_NEEDMINOR)<>0) then
|
|
begin
|
|
{ We may want to Exitan existing device }
|
|
si2:=LIST_FIRST(@csw^.d_devs);
|
|
while (si2<>nil) do
|
|
begin
|
|
if (dev2unit(si2)=_unit) then
|
|
begin
|
|
dev_free_devlocked(si);
|
|
Exit(si2);
|
|
end;
|
|
si2:=LIST_NEXT(si2,@si2^.si_list);
|
|
end;
|
|
end;
|
|
si^.si_drv0 :=_unit;
|
|
si^.si_devsw:=csw;
|
|
LIST_INSERT_HEAD(@csw^.d_devs,si,@si^.si_list);
|
|
Exit(si);
|
|
end;
|
|
|
|
procedure fini_cdevsw(devsw:p_cdevsw);
|
|
var
|
|
gt:p_cdevsw;
|
|
begin
|
|
if (devsw^.d_gianttrick<>nil) then
|
|
begin
|
|
gt:=devsw^.d_gianttrick;
|
|
Move(gt^, devsw^, sizeof(t_cdevsw));
|
|
cdevsw_free_devlocked(gt);
|
|
devsw^.d_gianttrick:=nil;
|
|
end;
|
|
devsw^.d_flags:=devsw^.d_flags and (not D_INIT);
|
|
end;
|
|
|
|
function prep_cdevsw(devsw:p_cdevsw;flags:Integer):Integer;
|
|
var
|
|
dsw2:p_cdevsw;
|
|
|
|
procedure FIXUP(member:PPointer;noop,giant:Pointer); inline;
|
|
begin
|
|
if (member^=nil) then
|
|
begin
|
|
member^:=noop;
|
|
end else
|
|
if ((devsw^.d_flags and D_NEEDGIANT)<>0) then
|
|
begin
|
|
member^:=giant;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
mtx_assert(devmtx);
|
|
if ((devsw^.d_flags and D_INIT)<>0) then
|
|
begin
|
|
Exit(0);
|
|
end;
|
|
if ((devsw^.d_flags and D_NEEDGIANT)<>0) then
|
|
begin
|
|
dev_unlock();
|
|
dsw2:=AllocMem(sizeof(t_cdevsw));
|
|
dev_lock();
|
|
if (dsw2=nil) and ((devsw^.d_flags and D_INIT)=0) then
|
|
begin
|
|
Exit(ENOMEM);
|
|
end;
|
|
end else
|
|
dsw2:=nil;
|
|
if ((devsw^.d_flags and D_INIT)<>0) then
|
|
begin
|
|
if (dsw2<>nil) then
|
|
begin
|
|
cdevsw_free_devlocked(dsw2);
|
|
end;
|
|
Exit(0);
|
|
end;
|
|
|
|
if (devsw^.d_version<>D_VERSION_03) then
|
|
begin
|
|
Writeln('WARNING: Device driver has wrong version');
|
|
devsw^.d_open :=d_open_t(@_enxio);
|
|
devsw^.d_close :=d_close_t(@_enxio);
|
|
devsw^.d_read :=d_read_t(@_enxio);
|
|
devsw^.d_write :=d_write_t(@_enxio);
|
|
devsw^.d_ioctl :=d_ioctl_t(@_enxio);
|
|
devsw^.d_poll :=d_poll_t(@_enodev);
|
|
devsw^.d_mmap :=d_mmap_t(@_enodev);
|
|
devsw^.d_mmap_single :=d_mmap_single_t(@_enodev);
|
|
devsw^.d_mmap_single2:=d_mmap_single2_t(@_enodev);
|
|
devsw^.d_strategy :=@dead_strategy;
|
|
devsw^.d_dump :=dumper_t(@_enxio);
|
|
devsw^.d_kqfilter :=d_kqfilter_t(@_enxio);
|
|
end;
|
|
|
|
if ((devsw^.d_flags and D_NEEDGIANT)<>0) then
|
|
begin
|
|
if (devsw^.d_gianttrick=nil) then
|
|
begin
|
|
Move(devsw^, dsw2^, sizeof(t_cdevsw));
|
|
devsw^.d_gianttrick:=dsw2;
|
|
dsw2:=nil;
|
|
end;
|
|
end;
|
|
|
|
FIXUP(@devsw^.d_open, @_nullop, @giant_open);
|
|
FIXUP(@devsw^.d_fdopen, nil, @giant_fdopen);
|
|
FIXUP(@devsw^.d_close, @_nullop, @giant_close);
|
|
FIXUP(@devsw^.d_read, @_enodev, @giant_read);
|
|
FIXUP(@devsw^.d_write, @_enodev, @giant_write);
|
|
FIXUP(@devsw^.d_ioctl, @_enodev, @giant_ioctl);
|
|
FIXUP(@devsw^.d_poll, @no_poll, @giant_poll);
|
|
FIXUP(@devsw^.d_mmap, @_enodev, @giant_mmap);
|
|
FIXUP(@devsw^.d_strategy, @no_strategy, @giant_strategy);
|
|
FIXUP(@devsw^.d_kqfilter, @_enodev, @giant_kqfilter);
|
|
FIXUP(@devsw^.d_mmap_single, @_enodev, @giant_mmap_single);
|
|
FIXUP(@devsw^.d_mmap_single2, @_enodev, @giant_mmap_single2);
|
|
|
|
if (devsw^.d_dump=nil) then devsw^.d_dump:=dumper_t(@_enodev);
|
|
|
|
LIST_INIT(@devsw^.d_devs);
|
|
|
|
devsw^.d_flags:=devsw^.d_flags or D_INIT;
|
|
|
|
if (dsw2<>nil) then
|
|
begin
|
|
cdevsw_free_devlocked(dsw2);
|
|
end;
|
|
|
|
Exit(0);
|
|
end;
|
|
|
|
function prep_devname(dev:p_cdev;fmt:PChar;const Args:Array of const):Integer; register;
|
|
var
|
|
R:RawByteString;
|
|
len:Integer;
|
|
from,q,s,_to:PChar;
|
|
begin
|
|
mtx_assert(devmtx);
|
|
|
|
R:=Format(fmt,Args);
|
|
len:=Length(R);
|
|
|
|
if (len > sizeof(dev^.__si_namebuf) - 1) then
|
|
begin
|
|
Exit(ENAMETOOLONG);
|
|
end;
|
|
|
|
Move(PChar(R)^,dev^.__si_namebuf,len);
|
|
|
|
{ Strip leading slashes. }
|
|
from:=@dev^.__si_namebuf;
|
|
while (from='/') do Inc(from);
|
|
|
|
_to:=@dev^.__si_namebuf;
|
|
while (from^<>#0) do
|
|
begin
|
|
{ Treat multiple sequential slashes as single. }
|
|
while (from[0]='/') and (from[1]='/') do
|
|
begin
|
|
Inc(from);
|
|
end;
|
|
{ Trailing slash is considered invalid. }
|
|
if (from[0]='/') and (from[1]=#0) then
|
|
begin
|
|
Exit(EINVAL);
|
|
end;
|
|
_to^:=from^;
|
|
//
|
|
Inc(from);
|
|
Inc(_to);
|
|
end;
|
|
_to^:=#0;
|
|
|
|
if (dev^.__si_namebuf[0]=#0) then
|
|
begin
|
|
Exit(EINVAL);
|
|
end;
|
|
|
|
{ Disallow '.' and '..' components. }
|
|
s:=@dev^.__si_namebuf;
|
|
while true do
|
|
begin
|
|
q:=s;
|
|
while (q^<>'/') and (q^<>#0) do Inc(q);
|
|
|
|
if (q - s=1) and (s[0]='.') then
|
|
begin
|
|
Exit(EINVAL);
|
|
end;
|
|
if (q - s=2) and (s[0]='.') and (s[1]='.') then
|
|
begin
|
|
Exit(EINVAL);
|
|
end;
|
|
if (q^<>'/') then
|
|
begin
|
|
break;
|
|
end;
|
|
s:=q + 1;
|
|
end;
|
|
|
|
if (devfs_dev_exists(dev^.__si_namebuf)<>0) then
|
|
begin
|
|
Exit(EEXIST);
|
|
end;
|
|
|
|
Exit(0);
|
|
end;
|
|
|
|
function make_dev_credv(flags:Integer;
|
|
dres:pp_cdev;
|
|
devsw:p_cdevsw;
|
|
_unit:Integer;
|
|
uid:uid_t;
|
|
gid:gid_t;
|
|
mode:Integer;
|
|
fmt:PChar;
|
|
const Args:Array of const):Integer; register; public name 'make_dev_credv';
|
|
var
|
|
dev,dev_new:p_cdev;
|
|
res:Integer;
|
|
begin
|
|
Assert(((flags and MAKEDEV_WAITOK)=0) or ((flags and MAKEDEV_NOWAIT)=0),'make_dev_credv: both WAITOK and NOWAIT specified');
|
|
dev_new:=devfs_alloc(flags);
|
|
if (dev_new=nil) then
|
|
begin
|
|
Exit(ENOMEM);
|
|
end;
|
|
dev_lock();
|
|
res:=prep_cdevsw(devsw, flags);
|
|
if (res<>0) then
|
|
begin
|
|
dev_unlock();
|
|
devfs_free(dev_new);
|
|
Exit(res);
|
|
end;
|
|
dev:=newdev(devsw, _unit, dev_new);
|
|
if ((dev^.si_flags and SI_NAMED)=0) then
|
|
begin
|
|
res:=prep_devname(dev, fmt, Args);
|
|
if (res<>0) then
|
|
begin
|
|
if ((flags and MAKEDEV_CHECKNAME)=0) then
|
|
begin
|
|
Assert(False,'make_dev_credv: bad si_name (error=%d, si_name=%s)');
|
|
end;
|
|
if (dev=dev_new) then
|
|
begin
|
|
LIST_REMOVE(dev,@dev^.si_list);
|
|
dev_unlock();
|
|
devfs_free(dev);
|
|
end else
|
|
dev_unlock();
|
|
Exit(res);
|
|
end;
|
|
end;
|
|
if ((flags and MAKEDEV_REF)<>0) then
|
|
begin
|
|
dev_refl(dev);
|
|
end;
|
|
if ((flags and MAKEDEV_ETERNAL)<>0) then
|
|
dev^.si_flags:=dev^.si_flags or SI_ETERNAL;
|
|
if ((dev^.si_flags and SI_CHEAPCLONE)<>0) and
|
|
((dev^.si_flags and SI_NAMED)<>0) then
|
|
begin
|
|
{
|
|
* This is allowed as it removes races and generally
|
|
* simplifies cloning devices.
|
|
* XXX: still ??
|
|
}
|
|
dev_unlock_and_free();
|
|
dres^:=dev;
|
|
Exit(0);
|
|
end;
|
|
Assert((dev^.si_flags and SI_NAMED)=0,'make_dev() by driver %s on pre-existing device (min=%x, name=%s)');
|
|
dev^.si_flags:=dev^.si_flags or SI_NAMED;
|
|
|
|
dev^.si_uid :=uid;
|
|
dev^.si_gid :=gid;
|
|
dev^.si_mode:=mode;
|
|
|
|
devfs_create(dev);
|
|
//clean_unrhdrl(devfs_inos);
|
|
dev_unlock_and_free();
|
|
|
|
notify_create(dev, flags);
|
|
|
|
dres^:=dev;
|
|
Exit(0);
|
|
end;
|
|
|
|
procedure dev_dependsl(pdev,cdev:p_cdev);
|
|
begin
|
|
cdev^.si_parent:=pdev;
|
|
cdev^.si_flags :=cdev^.si_flags or SI_CHILD;
|
|
LIST_INSERT_HEAD(@pdev^.si_children,cdev,@cdev^.si_siblings);
|
|
end;
|
|
|
|
procedure dev_depends(pdev,cdev:p_cdev);
|
|
begin
|
|
dev_lock();
|
|
dev_dependsl(pdev, cdev);
|
|
dev_unlock();
|
|
end;
|
|
|
|
function make_dev_alias_v(flags:Integer;
|
|
cdev:pp_cdev;
|
|
pdev:p_cdev;
|
|
fmt:PChar;
|
|
const Args:Array of const):Integer; register; public name 'make_dev_alias_v';
|
|
var
|
|
dev:p_cdev;
|
|
error:Integer;
|
|
begin
|
|
Assert(pdev<>nil,'make_dev_alias_v: pdev is nil');
|
|
Assert(((flags and MAKEDEV_WAITOK)=0) or ((flags and MAKEDEV_NOWAIT)=0),'make_dev_alias_v: both WAITOK and NOWAIT specified');
|
|
Assert((flags and (not (MAKEDEV_WAITOK or MAKEDEV_NOWAIT or MAKEDEV_CHECKNAME)))=0,'make_dev_alias_v: invalid flags specified (flags=%02x)');
|
|
|
|
dev:=devfs_alloc(flags);
|
|
if (dev=nil) then
|
|
begin
|
|
Exit(ENOMEM);
|
|
end;
|
|
dev_lock();
|
|
dev^.si_flags:=dev^.si_flags or SI_ALIAS;
|
|
error:=prep_devname(dev, fmt, Args);
|
|
if (error<>0) then
|
|
begin
|
|
if ((flags and MAKEDEV_CHECKNAME)=0) then
|
|
begin
|
|
Assert(False,'make_dev_alias_v: bad si_name');
|
|
end;
|
|
dev_unlock();
|
|
devfs_free(dev);
|
|
Exit(error);
|
|
end;
|
|
dev^.si_flags:=dev^.si_flags or SI_NAMED;
|
|
devfs_create(dev);
|
|
dev_dependsl(pdev, dev);
|
|
//clean_unrhdrl(devfs_inos);
|
|
dev_unlock();
|
|
|
|
notify_create(dev, flags);
|
|
cdev^:=dev;
|
|
|
|
Exit(0);
|
|
end;
|
|
|
|
function make_dev_physpath_alias(flags:Integer;
|
|
cdev:pp_cdev;
|
|
pdev,old_alias:p_cdev;
|
|
physpath:PChar):Integer; public;
|
|
label
|
|
_ret,
|
|
_out;
|
|
var
|
|
devfspath:PChar;
|
|
physpath_len:Integer;
|
|
max_parentpath_len:Integer;
|
|
parentpath_len:Integer;
|
|
devfspathbuf_len:Integer;
|
|
ret:Integer;
|
|
R:RawByteString;
|
|
begin
|
|
cdev^:=nil;
|
|
devfspath:=nil;
|
|
physpath_len:=strlen(physpath);
|
|
ret:=EINVAL;
|
|
if (physpath_len=0) then
|
|
begin
|
|
goto _out;
|
|
end;
|
|
|
|
if (strlcomp('id1,', physpath, 4)=0) then
|
|
begin
|
|
Inc(physpath,4);
|
|
Dec(physpath_len,4);
|
|
if (physpath_len=0) then
|
|
begin
|
|
goto _out;
|
|
end;
|
|
end;
|
|
|
|
max_parentpath_len:=SPECNAMELEN - physpath_len - 1;
|
|
parentpath_len:=strlen(pdev^.si_name);
|
|
if (max_parentpath_len < parentpath_len) then
|
|
begin
|
|
ret:=ENAMETOOLONG;
|
|
goto _out;
|
|
end;
|
|
|
|
devfspathbuf_len:=physpath_len + 1 + parentpath_len + 1;
|
|
devfspath:=AllocMem(devfspathbuf_len);
|
|
if (devfspath=nil) then
|
|
begin
|
|
ret:=ENOMEM;
|
|
goto _out;
|
|
end;
|
|
|
|
R:=Format('%s/%s',[physpath,pdev^.si_name]);
|
|
Move(PChar(R)^,devfspath^,Length(R)+1);
|
|
|
|
if (old_alias=nil) then goto _ret;
|
|
|
|
if (strcomp(old_alias^.si_name, devfspath)=0) then
|
|
begin
|
|
_ret:
|
|
{ Retain the existing alias. }
|
|
cdev^:=old_alias;
|
|
old_alias:=nil;
|
|
ret:=0;
|
|
end else
|
|
begin
|
|
ret:=make_dev_alias_p(flags, cdev, pdev, '%s', [devfspath]);
|
|
end;
|
|
_out:
|
|
if (old_alias<>nil) then
|
|
begin
|
|
destroy_dev(old_alias);
|
|
end;
|
|
if (devfspath<>nil) then
|
|
begin
|
|
FreeMem(devfspath);
|
|
end;
|
|
Exit(ret);
|
|
end;
|
|
|
|
procedure destroy_devl(dev:p_cdev);
|
|
var
|
|
csw:p_cdevsw;
|
|
p:p_cdev_privdata;
|
|
cdp:p_cdev_priv;
|
|
begin
|
|
|
|
mtx_assert(devmtx);
|
|
Assert((dev^.si_flags and SI_NAMED)<>0 ,'WARNING: Driver mistake: destroy_dev on %dn');
|
|
Assert((dev^.si_flags and SI_ETERNAL)=0,'WARNING: Driver mistake: destroy_dev on eternal %dn');
|
|
|
|
cdp:=cdev2priv(dev);
|
|
if ((cdp^.cdp_flags and CDP_UNREF_DTR)=0) then
|
|
begin
|
|
{
|
|
* Avoid race with dev_rel(), e.g. from the populate
|
|
* loop. If CDP_UNREF_DTR flag is set, the reference
|
|
* to be dropped at the end of destroy_devl() was
|
|
* already taken by delist_dev_locked().
|
|
}
|
|
dev_refl(dev);
|
|
|
|
devfs_destroy(dev);
|
|
end;
|
|
|
|
{ Remove name marking }
|
|
dev^.si_flags:=dev^.si_flags and (not SI_NAMED);
|
|
|
|
{ If we are a child, remove us from the parents list }
|
|
if ((dev^.si_flags and SI_CHILD)<>0) then
|
|
begin
|
|
LIST_REMOVE(dev,@dev^.si_siblings);
|
|
dev^.si_flags:=dev^.si_flags and (not SI_CHILD);
|
|
end;
|
|
|
|
{ Kill our children }
|
|
while (not LIST_EMPTY(@dev^.si_children)) do
|
|
begin
|
|
destroy_devl(LIST_FIRST(@dev^.si_children));
|
|
end;
|
|
|
|
{ Remove from clone list }
|
|
if ((dev^.si_flags and SI_CLONELIST)<>0) then
|
|
begin
|
|
LIST_REMOVE(dev,@dev^.si_clone);
|
|
dev^.si_flags:=dev^.si_flags and (not SI_CLONELIST);
|
|
end;
|
|
|
|
csw:=dev^.si_devsw;
|
|
dev^.si_devsw:=nil; { already nil for SI_ALIAS }
|
|
while (csw<>nil) and (dev^.si_threadcount<>0) do
|
|
begin
|
|
if (csw^.d_purge=nil) then Break;
|
|
csw^.d_purge(dev);
|
|
msleep(csw, @devmtx, PRIBIO, 'devprg', hz div 10);
|
|
if (dev^.si_threadcount<>0) then
|
|
begin
|
|
Writeln(Format('Still %lu threads in %sn',[dev^.si_threadcount, devtoname(dev)]));
|
|
end;
|
|
end;
|
|
while (dev^.si_threadcount<>0) do
|
|
begin
|
|
{ Use unique dummy wait ident }
|
|
msleep(@csw, @devmtx, PRIBIO, 'devdrn', hz div 10);
|
|
end;
|
|
|
|
dev_unlock();
|
|
if ((cdp^.cdp_flags and CDP_UNREF_DTR)=0) then
|
|
begin
|
|
{ avoid out of order notify events }
|
|
notify_destroy(dev);
|
|
end;
|
|
mtx_lock(cdevpriv_mtx);
|
|
p:=LIST_FIRST(@cdp^.cdp_fdpriv);
|
|
while (p<>nil) do
|
|
begin
|
|
devfs_destroy_cdevpriv(p);
|
|
mtx_lock(cdevpriv_mtx);
|
|
//
|
|
p:=LIST_FIRST(@cdp^.cdp_fdpriv);
|
|
end;
|
|
mtx_unlock(cdevpriv_mtx);
|
|
dev_lock();
|
|
|
|
dev^.si_drv1:=nil;
|
|
dev^.si_drv2:=nil;
|
|
FillChar(dev^.__si_namebuf, sizeof(dev^.__si_namebuf),0);
|
|
|
|
if ((dev^.si_flags and SI_ALIAS)=0) then
|
|
begin
|
|
{ Remove from cdevsw list }
|
|
LIST_REMOVE(dev,@dev^.si_list);
|
|
|
|
{ If cdevsw has no more struct cdev *'s, clean it }
|
|
if LIST_EMPTY(@csw^.d_devs) then
|
|
begin
|
|
fini_cdevsw(csw);
|
|
wakeup(@csw^.d_devs);
|
|
end;
|
|
end;
|
|
dev^.si_flags :=dev^.si_flags and (not SI_ALIAS);
|
|
cdp^.cdp_flags:=cdp^.cdp_flags and (not CDP_UNREF_DTR);
|
|
Dec(dev^.si_refcount);
|
|
|
|
if (dev^.si_refcount > 0) then
|
|
LIST_INSERT_HEAD(@dead_cdevsw.d_devs,dev,@dev^.si_list)
|
|
else
|
|
dev_free_devlocked(dev);
|
|
end;
|
|
|
|
procedure delist_dev_locked(dev:p_cdev);
|
|
var
|
|
cdp:p_cdev_priv;
|
|
child:p_cdev;
|
|
begin
|
|
mtx_assert(devmtx);
|
|
cdp:=cdev2priv(dev);
|
|
if ((cdp^.cdp_flags and CDP_UNREF_DTR)<>0) then Exit;
|
|
|
|
cdp^.cdp_flags:=cdp^.cdp_flags or CDP_UNREF_DTR;
|
|
dev_refl(dev);
|
|
devfs_destroy(dev);
|
|
|
|
child:=LIST_FIRST(@dev^.si_children);
|
|
while (child<>nil) do
|
|
begin
|
|
delist_dev_locked(child);
|
|
child:=LIST_NEXT(child,@child^.si_siblings);
|
|
end;
|
|
|
|
dev_unlock();
|
|
{ ensure the destroy event is queued in order }
|
|
notify_destroy(dev);
|
|
dev_lock();
|
|
end;
|
|
|
|
{
|
|
* This function will delist a character device and its children from
|
|
* the directory listing and create a destroy event without waiting
|
|
* for all character device references to go away. At some later point
|
|
* destroy_dev() must be called to complete the character device
|
|
* destruction. After calling this function the character device name
|
|
* can instantly be re-used.
|
|
}
|
|
procedure delist_dev(dev:p_cdev);
|
|
begin
|
|
dev_lock();
|
|
delist_dev_locked(dev);
|
|
dev_unlock();
|
|
end;
|
|
|
|
procedure destroy_dev(dev:p_cdev); public;
|
|
begin
|
|
dev_lock();
|
|
destroy_devl(dev);
|
|
dev_unlock_and_free();
|
|
end;
|
|
|
|
|
|
end.
|
|
|