unit dev_null;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface

uses
 sys_conf;

const
 ZERO_REGION_SIZE=(2 * 1024 * 1024); // 2MB

var
 zero_region:Pointer;

procedure init_zero_region;
procedure free_zero_region;

Function null_modevent(_mod,_type:Integer):Integer; //DEV_MODULE(nil, null_modevent, nil);

implementation

uses
 errno,
 vuio,
 vdisk,
 vfilio,
 subr_uio;

var
{ For use with destroy_dev(9). }
 null_dev:p_cdev;
 zero_dev:p_cdev;

Function null_write(dev:p_cdev;uio:p_uio;ioflag:Integer):Integer;             forward;
Function null_ioctl(dev:p_cdev;cmd:QWORD;data:Pointer;fflag:Integer):Integer; forward;
Function zero_ioctl(dev:p_cdev;cmd:QWORD;data:Pointer;fflag:Integer):Integer; forward;
Function zero_read(dev:p_cdev;uio:p_uio;ioflag:Integer):Integer;              forward;

const
 null_cdevsw:t_cdevsw=(
  d_version     :D_VERSION;
  d_flags       :0;
  d_name        :'null';
  d_read        :d_read_t(@_nullop);
  d_write       :@null_write;
  d_ioctl       :@null_ioctl;
 );

 zero_cdevsw:t_cdevsw=(
  d_version     :D_VERSION;
  d_flags       :D_MMAP_ANON;
  d_name        :'zero';
  d_read        :@zero_read;
  d_write       :@null_write;
  d_ioctl       :@zero_ioctl;
 );

{ ARGSUSED }
Function null_write(dev:p_cdev;uio:p_uio;ioflag:Integer):Integer;
begin
 uio^.uio_resid:=0;

 Exit(0);
end;

{ ARGSUSED }
Function null_ioctl(dev:p_cdev;cmd:QWORD;data:Pointer;fflag:Integer):Integer;
var
 error:Integer;
begin
 error:=0;

 case (cmd) of
  DIOCSKERNELDUMP:
   begin
    error:=EPERM;
    //error:=priv_check(td, PRIV_SETDUMPER);
    //if (error=0)
    // error:=set_dumper(nil);
   end;
  FIONBIO:;
  FIOASYNC:
   begin
    if (PInteger(data)^<>0) then
    begin
     error:=EINVAL;
    end;
   end;
  else
  begin
   error:=ENOIOCTL;
  end;
 end;

 Exit(error);
end;

{ ARGSUSED }
Function zero_ioctl(dev:p_cdev;cmd:QWORD;data:Pointer;fflag:Integer):Integer;
var
 error:Integer;
begin
 error:=0;

 case (cmd) of
  FIONBIO:;
  FIOASYNC:
   begin
    if (PInteger(data)^<>0) then
    begin
     error:=EINVAL;
    end;
   end;
  else
  begin
   error:=ENOIOCTL;
  end;
 end;

 Exit(error);
end;

procedure init_zero_region;
var
 buf:Pointer;
begin
 if (zero_region=nil) then
 begin
  buf:=AllocMem(ZERO_REGION_SIZE);
  if (System.InterlockedCompareExchange(zero_region,buf,nil)<>nil) then
  begin
   FreeMem(buf);
  end;
 end;
end;

procedure free_zero_region;
var
 buf:Pointer;
begin
 buf:=System.InterlockedExchange(zero_region,nil);
 FreeMem(buf);
end;

{ ARGSUSED }
Function zero_read(dev:p_cdev;uio:p_uio;ioflag:Integer):Integer;
var
 zbuf:Pointer;
 len:QWORD;
 error:Integer;
begin
 error:=0;

 Assert(uio^.uio_rw=UIO_READ,'Cant be in %s for write');

 init_zero_region;

 zbuf:=zero_region;
 while (uio^.uio_resid > 0) and (error=0) do
 begin
  len:=uio^.uio_resid;
  if (len > ZERO_REGION_SIZE) then
  begin
   len:=ZERO_REGION_SIZE;
  end;
  error:=uiomove(zbuf, len, uio);
 end;

 Exit(error);
end;

{ ARGSUSED }
Function null_modevent(_mod,_type:Integer):Integer;
begin
 case (_type) of
  MOD_LOAD:
   begin
    null_dev:=make_dev_credf(0, @null_cdevsw, 0, UID_ROOT, GID_WHEEL, &666, 'null',[]);
    zero_dev:=make_dev_credf(0, @zero_cdevsw, 0, UID_ROOT, GID_WHEEL, &666, 'zero',[]);
   end;

  MOD_UNLOAD:
   begin
    destroy_dev(null_dev);
    destroy_dev(zero_dev);
    free_zero_region;
   end;

  MOD_SHUTDOWN:;

  else
   Exit(EOPNOTSUPP);
 end;

 Exit(0);
end;

end.

